Explorar o código

Merge branch 'master' into hurx

hurx hai 1 mes
pai
achega
b152b17262
Modificáronse 52 ficheiros con 4034 adicións e 1464 borrados
  1. 1 3
      .vscode/settings.json
  2. 3 3
      README.md
  3. 0 1
      src/api/home/index.ts
  4. 12 0
      src/api/pc/enterprise/good.ts
  5. 88 2
      src/api/pc/enterprise/order.ts
  6. 60 0
      src/api/pc/enterprise/orderTypes.ts
  7. 0 3
      src/api/pc/enterprise/types.ts
  8. 0 1
      src/api/search/index.ts
  9. 1 1
      src/api/system/user/types.ts
  10. 4 10
      src/api/workflow/spel/types.ts
  11. 104 51
      src/assets/styles/common.scss
  12. 3 3
      src/assets/styles/index.scss
  13. 3 5
      src/assets/styles/sidebar.scss
  14. 10 7
      src/components/EmptyState/index.vue
  15. 90 41
      src/components/HeaderBar/index.vue
  16. 1 1
      src/components/PageTitle/index.vue
  17. 60 29
      src/components/ProductCard/index.vue
  18. 39 14
      src/components/ProductItem/index.vue
  19. 36 33
      src/components/SearchBar/index.vue
  20. 7 7
      src/components/StatCards/index.vue
  21. 28 18
      src/components/StatusTabs/index.vue
  22. 19 40
      src/components/TableActions/index.vue
  23. 6 6
      src/components/TablePagination/index.vue
  24. 11 22
      src/components/index.ts
  25. 6 6
      src/constants/status.ts
  26. 0 1
      src/layout/components/breadcrumb.vue
  27. 0 1
      src/types/components.d.ts
  28. 0 1
      src/utils/devtools-protection.ts
  29. 8 0
      src/utils/siteConfig.ts
  30. 5 5
      src/utils/status.ts
  31. 20 16
      src/views/analysis/deptPurchase/index.vue
  32. 143 56
      src/views/analysis/orderAnalysis/index.vue
  33. 119 19
      src/views/analysis/orderStatus/index.vue
  34. 60 32
      src/views/analysis/purchaseDetail/index.vue
  35. 109 19
      src/views/analysis/settlementStatus/index.vue
  36. 157 25
      src/views/enterprise/messageNotice/index.vue
  37. 114 77
      src/views/enterprise/myFootprint/index.vue
  38. 467 317
      src/views/enterprise/purchaseHabit/index.vue
  39. 547 113
      src/views/enterprise/purchaseHistory/index.vue
  40. 107 24
      src/views/enterprise/purchasePlan/index.vue
  41. 82 64
      src/views/enterprise/securitySetting/changePhone.vue
  42. 151 34
      src/views/enterprise/securitySetting/index.vue
  43. 74 56
      src/views/enterprise/securitySetting/resetPassword.vue
  44. 352 68
      src/views/order/batchOrder/index.vue
  45. 581 44
      src/views/organization/approvalFlow/create.vue
  46. 87 30
      src/views/organization/approvalFlow/index.vue
  47. 151 51
      src/views/organization/groupEnterprise/index.vue
  48. 6 2
      src/views/organization/itemExpense/index.vue
  49. 9 6
      src/views/valueAdded/maintenance/index.vue
  50. 1 8
      tsconfig.json
  51. 1 1
      vite/plugins/icons.ts
  52. 91 87
      yoe-shop-web接口文档.md

+ 1 - 3
.vscode/settings.json

@@ -1,5 +1,3 @@
 {
-    "i18n-ally.localesPaths": [
-        "src/lang"
-    ]
+  "i18n-ally.localesPaths": ["src/lang"]
 }

+ 3 - 3
README.md

@@ -6,10 +6,10 @@
 
 ## 配套后端代码仓库地址
 
-| 介绍         | 项目名              | 项目地址                                                                                                                                                                           |
-|------------|:-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| 介绍              | 项目名           | 项目地址                                                                                                                                                                       |
+| ----------------- | :--------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
 | 🔥 分布式集群框架 | RuoYi-Vue-Plus   | - [Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus)<br> - [GitHub](https://github.com/dromara/RuoYi-Vue-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Vue-Plus)      |
-| 🔥 微服务框架   | RuoYi-Cloud-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus)<br>- [GitHub](https://github.com/dromara/RuoYi-Cloud-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Cloud-Plus) |
+| 🔥 微服务框架     | RuoYi-Cloud-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus)<br>- [GitHub](https://github.com/dromara/RuoYi-Cloud-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Cloud-Plus) |
 
 ## 分支说明
 

+ 0 - 1
src/api/home/index.ts

@@ -162,7 +162,6 @@ export const getClassificationFloorDetail2 = (id: any) => {
   });
 };
 
-
 //项目案例标题(平台装修-项目案例)
 
 export function getProjectCaseTitle(query: any) {

+ 12 - 0
src/api/pc/enterprise/good.ts

@@ -0,0 +1,12 @@
+import request from '@/utils/request';
+
+/**
+ * 查看协议供货的商品
+ * */
+export function getProtocolSupplyGoods(params: any) {
+  return request({
+    url: '/goods/protocolSupplyGoods',
+    method: 'get',
+    params
+  });
+}

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

@@ -1,5 +1,5 @@
 import request from '@/utils/request';
-import { OrderMain, OrderProduct, OrderStatusStats } from './orderTypes';
+import { OrderMain, OrderProduct, OrderStatusStats, OrderCustomerFlowSaveBo, OrderCustomerFlowLinkBo, OrderCustomerFlow } from './orderTypes';
 
 // ==================== 订单管理 ====================
 
@@ -100,7 +100,7 @@ export function batchConfirmation(orderIds: number[]) {
   });
 }
 
-// ==================== 订单管理 ====================
+// ==================== 订单评价 ====================
 
 /**
  * 查询当前企业的已评价订单
@@ -123,3 +123,89 @@ export function addOrderEvaluation(data: any) {
     data: data
   });
 }
+
+// ==================== 订单流程管理 ====================
+
+/**
+ * 新增订单流程(包含节点)
+ */
+export function addOrderFlow(data: OrderCustomerFlowSaveBo) {
+  return request({
+    url: '/order/pcOrderFlow/save',
+    method: 'post',
+    data: data
+  });
+}
+
+/**
+ * 编辑订单流程(包含节点)
+ */
+export function updateOrderFlow(data: OrderCustomerFlowSaveBo) {
+  return request({
+    url: '/order/pcOrderFlow/save',
+    method: 'put',
+    data: data
+  });
+}
+
+/**
+ * 删除订单流程
+ */
+export function deleteOrderFlow(id: number) {
+  return request({
+    url: `/order/pcOrderFlow/${id}`,
+    method: 'delete'
+  });
+}
+
+/**
+ * 审核订单流程
+ */
+export function auditOrderFlow(data: OrderCustomerFlowLinkBo) {
+  return request({
+    url: '/order/pcOrderFlow/audit',
+    method: 'put',
+    data: data
+  });
+}
+
+/**
+ * 查询订单流程详情
+ */
+export function getOrderFlowDetail(id: number) {
+  return request({
+    url: `/order/pcOrderFlow/${id}`,
+    method: 'get'
+  });
+}
+
+/**
+ * 查询订单流程列表
+ */
+export function getOrderFlowList(params?: any) {
+  return request({
+    url: '/order/pcOrderFlow/list',
+    method: 'get',
+    params: params
+  });
+}
+
+/**
+ * 开启订单流程
+ */
+export function startOrderFlow(id: number) {
+  return request({
+    url: `/order/pcOrderFlow/startFlow/${id}`,
+    method: 'put'
+  });
+}
+
+/**
+ * 关闭订单流程
+ */
+export function closeOrderFlow(id: number) {
+  return request({
+    url: `/order/pcOrderFlow/closeFlow/${id}`,
+    method: 'put'
+  });
+}

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

@@ -67,3 +67,63 @@ export interface OrderStatusStats {
   closedCount?: number;
   totalCount?: number;
 }
+
+/**
+ * 订单流程保存BO
+ * 用于新增或编辑订单流程
+ */
+export interface OrderCustomerFlowSaveBo {
+  id?: number | string;
+  flowName: string;
+  flowDesc?: string;
+  flowType?: string;
+  status?: string;
+  remark?: string;
+  flowNodes?: OrderCustomerFlowNode[];
+  [key: string]: any;
+}
+
+/**
+ * 订单流程节点
+ */
+export interface OrderCustomerFlowNode {
+  id?: number | string;
+  flowId?: number;
+  nodeName: string;
+  //节点类型(0开始节点 1中间节点 2结束节点)
+  nodeType: string;
+  //节点顺序(开始节点默认是0,结束节点最大)
+  sort: number;
+  //处理人id
+  handlerId?: string;
+  remark?: string;
+  [key: string]: any;
+}
+
+/**
+ * 订单流程审核BO
+ */
+export interface OrderCustomerFlowLinkBo {
+  id: number | string;
+  auditStatus: string;
+  auditOpinion?: string;
+  [key: string]: any;
+}
+
+/**
+ * 订单流程详情
+ */
+export interface OrderCustomerFlow {
+  id?: number | string;
+  flowName?: string;
+  flowDesc?: string;
+  flowType?: string;
+  status?: string;
+  createBy?: string;
+  createTime?: string;
+  updateBy?: string;
+  updateTime?: string;
+  remark?: string;
+  flowNodes?: OrderCustomerFlowNode[];
+  [key: string]: any;
+}

+ 0 - 3
src/api/pc/enterprise/types.ts

@@ -120,6 +120,3 @@ export interface ProductFavorites {
   remark?: string;
   [key: string]: any;
 }
-
-
-

+ 0 - 1
src/api/search/index.ts

@@ -27,7 +27,6 @@ export const getSpecialCategoryList = (query: any) => {
   });
 };
 
-
 //特价商品品牌
 export const getSpecialBrandList = (query: any) => {
   return request({

+ 1 - 1
src/api/system/user/types.ts

@@ -20,7 +20,7 @@ export interface UserQuery extends PageQuery {
   status?: string;
   deptId?: string | number;
   roleId?: string | number;
-  userIds?:  string | number | (string | number)[] | undefined;
+  userIds?: string | number | (string | number)[] | undefined;
 }
 
 /**

+ 4 - 10
src/api/workflow/spel/types.ts

@@ -33,7 +33,6 @@ export interface SpelVO {
    * 备注
    */
   remark?: string;
-
 }
 
 export interface SpelForm extends BaseEntity {
@@ -71,11 +70,9 @@ export interface SpelForm extends BaseEntity {
    * 备注
    */
   remark?: string;
-
 }
 
 export interface SpelQuery extends PageQuery {
-
   /**
    * 组件名称
    */
@@ -101,11 +98,8 @@ export interface SpelQuery extends PageQuery {
    */
   status?: string;
 
-    /**
-     * 日期范围参数
-     */
-    params?: any;
+  /**
+   * 日期范围参数
+   */
+  params?: any;
 }
-
-
-

+ 104 - 51
src/assets/styles/common.scss

@@ -1,59 +1,112 @@
 /* 布局flex */
-.flex-row-between{display: -webkit-flex;display: flex;justify-content: space-between;align-items: center;}
-.flex-row-around{display: -webkit-flex;display: flex;justify-content: space-around;align-items: center;}
-.flex-row-center{display: -webkit-flex;display: flex;justify-content: center; align-items: center;}
-.flex-row-start{display: -webkit-flex;display: flex;justify-content: flex-start; align-items: center;}
-.flex-row-end{display: -webkit-flex;display: flex;justify-content: flex-end; align-items: center;}
-.flex-column-between{display: -webkit-box;display: flex;flex-direction: column;justify-content: space-between;}
-.flex-column-center{display: -webkit-box;display: flex;flex-direction: column;justify-content: center;align-items: center;}
-.flex-1{flex: 1;}
-.flex-wrap{flex-wrap: wrap;}
-.flex-start{align-items: flex-start;}
-.inline-block{display: inline-block;}
+.flex-row-between {
+  display: -webkit-flex;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.flex-row-around {
+  display: -webkit-flex;
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+}
+.flex-row-center {
+  display: -webkit-flex;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.flex-row-start {
+  display: -webkit-flex;
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+}
+.flex-row-end {
+  display: -webkit-flex;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+}
+.flex-column-between {
+  display: -webkit-box;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+}
+.flex-column-center {
+  display: -webkit-box;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+.flex-1 {
+  flex: 1;
+}
+.flex-wrap {
+  flex-wrap: wrap;
+}
+.flex-start {
+  align-items: flex-start;
+}
+.inline-block {
+  display: inline-block;
+}
 
-.ellipsis{overflow: hidden;white-space: nowrap;text-overflow: ellipsis;}
-.ellipsis2{display: -webkit-box;-webkit-line-clamp: 2;line-clamp: 2; /* 添加标准属性 */-webkit-box-orient: vertical;overflow: hidden;text-overflow: ellipsis;}
+.ellipsis {
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+.ellipsis2 {
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  line-clamp: 2; /* 添加标准属性 */
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
 
+//分页
+.pagination-bos {
+  width: 1200px;
+  margin: 0 auto;
+  padding-bottom: 60px;
+  :deep(.el-select__wrapper) {
+    background: #f4f4f4;
+    box-shadow: 0 0 0 1px #e5e6eb inset;
+    border-radius: 2px;
+  }
+  :deep(.el-select__placeholder) {
+    color: #1d2129;
+  }
+  :deep(.el-input__wrapper) {
+    background: #f4f4f4;
+    box-shadow: 0 0 0 1px #e5e6eb inset;
+    border-radius: 2px;
+  }
+  :deep(.el-input__inner) {
+    color: #1d2129;
+  }
 
-  //分页
-  .pagination-bos {
-    width: 1200px;
-    margin: 0 auto;
-    padding-bottom: 60px;
-    :deep(.el-select__wrapper) {
-      background: #f4f4f4;
-      box-shadow: 0 0 0 1px #e5e6eb inset;
-      border-radius: 2px;
-    }
-    :deep(.el-select__placeholder) {
-      color: #1d2129;
-    }
-    :deep(.el-input__wrapper) {
-      background: #f4f4f4;
-      box-shadow: 0 0 0 1px #e5e6eb inset;
-      border-radius: 2px;
-    }
-    :deep(.el-input__inner) {
-      color: #1d2129;
-    }
-
-    :deep(.btn-prev) {
-      background: #f4f4f4;
-      border: 1px solid #e5e6eb;
-      margin-right: 8px;
-    }
-    :deep(.btn-next) {
+  :deep(.btn-prev) {
+    background: #f4f4f4;
+    border: 1px solid #e5e6eb;
+    margin-right: 8px;
+  }
+  :deep(.btn-next) {
+    background: #f4f4f4;
+    border: 1px solid #e5e6eb;
+    margin-left: 8px;
+  }
+  :deep(.el-pager) {
+    gap: 0 8px;
+    li {
       background: #f4f4f4;
       border: 1px solid #e5e6eb;
-      margin-left: 8px;
-    }
-    :deep(.el-pager) {
-      gap: 0 8px;
-      li {
-        background: #f4f4f4;
-        border: 1px solid #e5e6eb;
-        color: #1d2129;
-      }
+      color: #1d2129;
     }
   }
-
+}

+ 3 - 3
src/assets/styles/index.scss

@@ -121,8 +121,8 @@ aside {
   display: block;
   line-height: 32px;
   font-size: 16px;
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-    sans-serif;
+  font-family:
+    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
   color: #2c3e50;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
@@ -143,7 +143,7 @@ aside {
 }
 
 // search面板样式
-.panel{
+.panel {
   margin-bottom: 0.75rem;
   border-radius: 0.25rem;
   border: 1px solid var(--el-border-color-light);

+ 3 - 5
src/assets/styles/sidebar.scss

@@ -193,7 +193,6 @@
       }
     }
 
-
     & .el-sub-menu {
       overflow: hidden;
       border-radius: 8px;
@@ -207,7 +206,6 @@
         background-color: var(--el-menu-active-color) !important;
       }
 
-
       & > .el-sub-menu__title {
         padding: 0 !important;
       }
@@ -285,12 +283,12 @@
   }
 }
 // 收起菜单后悬浮的菜单样式
-.el-popper.is-pure{
+.el-popper.is-pure {
   border-radius: 8px;
-  .el-menu--popup{
+  .el-menu--popup {
     border-radius: 8px;
   }
-  .el-menu-item{
+  .el-menu-item {
     border-radius: 4px;
   }
 }

+ 10 - 7
src/components/EmptyState/index.vue

@@ -9,13 +9,16 @@
 </template>
 
 <script setup lang="ts">
-withDefaults(defineProps<{
-  description?: string
-  imageSize?: number
-}>(), {
-  description: '暂无数据',
-  imageSize: 120
-})
+withDefaults(
+  defineProps<{
+    description?: string;
+    imageSize?: number;
+  }>(),
+  {
+    description: '暂无数据',
+    imageSize: 120
+  }
+);
 </script>
 
 <style scoped lang="scss">

+ 90 - 41
src/components/HeaderBar/index.vue

@@ -3,16 +3,22 @@
     <!-- 顶部工具栏 -->
     <div class="header-top">
       <div class="header-top-content">
-        <div class="location"><el-icon><Location /></el-icon> 武汉</div>
+        <div class="location">
+          <el-icon><Location /></el-icon> 武汉
+        </div>
         <div class="top-links">
           <span>您好,请登录</span>
           <span class="link highlight">免费注册</span>
           <span class="divider">|</span>
           <span class="link">工作台</span>
           <span class="divider">|</span>
-          <span class="link">采购合作 <el-icon><ArrowDown /></el-icon></span>
+          <span class="link"
+            >采购合作 <el-icon><ArrowDown /></el-icon
+          ></span>
           <span class="divider">|</span>
-          <span class="link">客户服务 <el-icon><ArrowDown /></el-icon></span>
+          <span class="link"
+            >客户服务 <el-icon><ArrowDown /></el-icon
+          ></span>
           <span class="divider">|</span>
           <span class="link">帮助中心</span>
           <span class="divider">|</span>
@@ -30,7 +36,9 @@
         <div class="search-box">
           <el-input v-model="searchKeyword" placeholder="搜索商品、品牌、分类..." class="search-input" @keyup.enter="handleSearch">
             <template #append>
-              <el-button type="danger" class="search-btn" @click="handleSearch"><el-icon><Search /></el-icon></el-button>
+              <el-button type="danger" class="search-btn" @click="handleSearch"
+                ><el-icon><Search /></el-icon
+              ></el-button>
             </template>
           </el-input>
           <div class="hot-keywords">
@@ -38,7 +46,9 @@
           </div>
         </div>
         <div class="cart-box">
-          <el-button class="cart-btn" @click="handleCartClick"><el-icon><ShoppingCart /></el-icon> 我的采购车</el-button>
+          <el-button class="cart-btn" @click="handleCartClick"
+            ><el-icon><ShoppingCart /></el-icon> 我的采购车</el-button
+          >
         </div>
         <div class="qrcode-box">
           <div class="qrcode-placeholder"></div>
@@ -49,40 +59,40 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
-import { Location, ArrowDown, Search, ShoppingCart } from '@element-plus/icons-vue'
+import { ref } from 'vue';
+import { Location, ArrowDown, Search, ShoppingCart } from '@element-plus/icons-vue';
 
-const searchKeyword = ref('')
-const hotKeywords = ['家纺', '打印机', '打印耗材', '空调', '取暖', '开门红', '劳保福利']
+const searchKeyword = ref('');
+const hotKeywords = ['家纺', '打印机', '打印耗材', '空调', '取暖', '开门红', '劳保福利'];
 
 const emit = defineEmits<{
-  search: [keyword: string]
-  cartClick: []
-}>()
+  search: [keyword: string];
+  cartClick: [];
+}>();
 
 const handleSearch = () => {
-  emit('search', searchKeyword.value)
-}
+  emit('search', searchKeyword.value);
+};
 
 const handleKeywordClick = (word: string) => {
-  searchKeyword.value = word
-  emit('search', word)
-}
+  searchKeyword.value = word;
+  emit('search', word);
+};
 
 const handleCartClick = () => {
-  emit('cartClick')
-}
+  emit('cartClick');
+};
 </script>
 
 <style scoped lang="scss">
 .layout-header {
   background: #fff;
-  
+
   .header-top {
     background: #f5f5f5;
     font-size: 12px;
     color: #666;
-    
+
     .header-top-content {
       max-width: 1200px;
       margin: 0 auto;
@@ -91,33 +101,39 @@ const handleCartClick = () => {
       justify-content: space-between;
       align-items: center;
     }
-    
+
     .location {
       display: flex;
       align-items: center;
       gap: 4px;
     }
-    
+
     .top-links {
       display: flex;
       align-items: center;
       gap: 8px;
-      
+
       .link {
         cursor: pointer;
         display: flex;
         align-items: center;
         gap: 2px;
-        &:hover { color: #e60012; }
-        &.highlight { color: #e60012; }
+        &:hover {
+          color: #e60012;
+        }
+        &.highlight {
+          color: #e60012;
+        }
+      }
+      .divider {
+        color: #ddd;
       }
-      .divider { color: #ddd; }
     }
   }
-  
+
   .header-main {
     border-bottom: 2px solid #e60012;
-    
+
     .header-main-content {
       max-width: 1200px;
       margin: 0 auto;
@@ -126,43 +142,76 @@ const handleCartClick = () => {
       align-items: center;
       gap: 40px;
     }
-    
+
     .logo {
       display: flex;
       align-items: center;
       gap: 8px;
       cursor: pointer;
-      .logo-img { height: 50px; }
-      .logo-text { font-size: 28px; font-weight: bold; color: #e60012; }
+      .logo-img {
+        height: 50px;
+      }
+      .logo-text {
+        font-size: 28px;
+        font-weight: bold;
+        color: #e60012;
+      }
     }
-    
+
     .search-box {
       flex: 1;
       .search-input {
-        :deep(.el-input__wrapper) { border-radius: 0; }
-        :deep(.el-input-group__append) { padding: 0; background: #e60012; border: none; }
+        :deep(.el-input__wrapper) {
+          border-radius: 0;
+        }
+        :deep(.el-input-group__append) {
+          padding: 0;
+          background: #e60012;
+          border: none;
+        }
+      }
+      .search-btn {
+        background: #e60012;
+        border: none;
+        color: #fff;
+        height: 40px;
+        width: 60px;
+        border-radius: 0;
       }
-      .search-btn { background: #e60012; border: none; color: #fff; height: 40px; width: 60px; border-radius: 0; }
       .hot-keywords {
         margin-top: 8px;
         display: flex;
         gap: 16px;
-        .keyword { font-size: 12px; color: #666; cursor: pointer; &:hover { color: #e60012; } }
+        .keyword {
+          font-size: 12px;
+          color: #666;
+          cursor: pointer;
+          &:hover {
+            color: #e60012;
+          }
+        }
       }
     }
-    
+
     .cart-box {
       .cart-btn {
         border: 1px solid #e60012;
         color: #e60012;
         background: #fff;
         padding: 10px 20px;
-        &:hover { background: #fff5f5; }
+        &:hover {
+          background: #fff5f5;
+        }
       }
     }
-    
+
     .qrcode-box {
-      .qrcode-placeholder { width: 80px; height: 80px; background: #f5f5f5; border: 1px solid #eee; }
+      .qrcode-placeholder {
+        width: 80px;
+        height: 80px;
+        background: #f5f5f5;
+        border: 1px solid #eee;
+      }
     }
   }
 }

+ 1 - 1
src/components/PageTitle/index.vue

@@ -7,7 +7,7 @@
 </template>
 
 <script setup lang="ts">
-defineProps<{ title: string }>()
+defineProps<{ title: string }>();
 </script>
 
 <style scoped lang="scss">

+ 60 - 29
src/components/ProductCard/index.vue

@@ -30,38 +30,43 @@
 </template>
 
 <script setup lang="ts">
-import { ref, watch } from 'vue'
-import { Picture, Plus } from '@element-plus/icons-vue'
+import { ref, watch } from 'vue';
+import { Picture, Plus } from '@element-plus/icons-vue';
 
 interface Product {
-  image?: string
-  name: string
-  price: string | number
-  originalPrice?: string | number
-  tag?: string
+  image?: string;
+  name: string;
+  price: string | number;
+  originalPrice?: string | number;
+  tag?: string;
 }
 
 const props = defineProps<{
-  product: Product
-  modelValue?: boolean
-  showCheckbox?: boolean
-  showAction?: boolean
-  showAddCart?: boolean
-  actionText?: string
-}>()
+  product: Product;
+  modelValue?: boolean;
+  showCheckbox?: boolean;
+  showAction?: boolean;
+  showAddCart?: boolean;
+  actionText?: string;
+}>();
 
 const emit = defineEmits<{
-  'update:modelValue': [value: boolean]
-  'action': []
-  'addCart': []
-}>()
+  'update:modelValue': [value: boolean];
+  'action': [];
+  'addCart': [];
+}>();
 
-const checked = ref(props.modelValue || false)
-watch(() => props.modelValue, (val) => { checked.value = val || false })
+const checked = ref(props.modelValue || false);
+watch(
+  () => props.modelValue,
+  (val) => {
+    checked.value = val || false;
+  }
+);
 
 const handleCheck = (val: boolean | string | number) => {
-  emit('update:modelValue', !!val)
-}
+  emit('update:modelValue', !!val);
+};
 </script>
 
 <style scoped lang="scss">
@@ -80,7 +85,9 @@ const handleCheck = (val: boolean | string | number) => {
       font-size: 12px;
       color: #999;
       cursor: pointer;
-      &:hover { color: #e60012; }
+      &:hover {
+        color: #e60012;
+      }
     }
   }
 
@@ -91,8 +98,18 @@ const handleCheck = (val: boolean | string | number) => {
     align-items: center;
     justify-content: center;
     padding: 10px;
-    .el-image { width: 100%; height: 100%; }
-    .image-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: #f5f5f5; }
+    .el-image {
+      width: 100%;
+      height: 100%;
+    }
+    .image-placeholder {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      background: #f5f5f5;
+    }
   }
 
   .product-info {
@@ -113,9 +130,20 @@ const handleCheck = (val: boolean | string | number) => {
       align-items: center;
       gap: 5px;
       position: relative;
-      .price-tag { font-size: 12px; color: #e60012; }
-      .current-price { font-size: 16px; font-weight: bold; color: #e60012; }
-      .original-price { font-size: 12px; color: #999; text-decoration: line-through; }
+      .price-tag {
+        font-size: 12px;
+        color: #e60012;
+      }
+      .current-price {
+        font-size: 16px;
+        font-weight: bold;
+        color: #e60012;
+      }
+      .original-price {
+        font-size: 12px;
+        color: #999;
+        text-decoration: line-through;
+      }
       .add-cart {
         position: absolute;
         right: 0;
@@ -130,7 +158,10 @@ const handleCheck = (val: boolean | string | number) => {
         justify-content: center;
         cursor: pointer;
         transition: all 0.2s;
-        &:hover { background: #e60012; color: #fff; }
+        &:hover {
+          background: #e60012;
+          color: #fff;
+        }
       }
     }
   }

+ 39 - 14
src/components/ProductItem/index.vue

@@ -19,18 +19,18 @@
 </template>
 
 <script setup lang="ts">
-import { Picture } from '@element-plus/icons-vue'
+import { Picture } from '@element-plus/icons-vue';
 
 interface Product {
-  image?: string
-  name: string
-  spec1?: string
-  spec2?: string
-  price: string | number
-  quantity: number
+  image?: string;
+  name: string;
+  spec1?: string;
+  spec2?: string;
+  price: string | number;
+  quantity: number;
 }
 
-defineProps<{ product: Product }>()
+defineProps<{ product: Product }>();
 </script>
 
 <style scoped lang="scss">
@@ -48,17 +48,42 @@ defineProps<{ product: Product }>()
     border-radius: 4px;
     overflow: hidden;
     flex-shrink: 0;
-    .el-image { width: 100%; height: 100%; }
-    .image-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
+    .el-image {
+      width: 100%;
+      height: 100%;
+    }
+    .image-placeholder {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
   }
 
   .product-detail {
     flex: 1;
-    .product-name { font-size: 14px; color: #333; margin-bottom: 5px; line-height: 1.4; }
-    .product-spec { font-size: 12px; color: #999; margin-bottom: 5px; }
-    .product-price { font-size: 16px; font-weight: bold; color: #e60012; }
+    .product-name {
+      font-size: 14px;
+      color: #333;
+      margin-bottom: 5px;
+      line-height: 1.4;
+    }
+    .product-spec {
+      font-size: 12px;
+      color: #999;
+      margin-bottom: 5px;
+    }
+    .product-price {
+      font-size: 16px;
+      font-weight: bold;
+      color: #e60012;
+    }
   }
 
-  .product-quantity { font-size: 13px; color: #666; }
+  .product-quantity {
+    font-size: 13px;
+    color: #666;
+  }
 }
 </style>

+ 36 - 33
src/components/SearchBar/index.vue

@@ -39,82 +39,85 @@
 </template>
 
 <script setup lang="ts">
-import { Search } from '@element-plus/icons-vue'
+import { Search } from '@element-plus/icons-vue';
 
 export interface FilterOption {
-  label: string
-  value: string
+  label: string;
+  value: string;
 }
 
 export interface FilterConfig {
-  field: string
-  label: string
-  options: FilterOption[]
+  field: string;
+  label: string;
+  options: FilterOption[];
 }
 
-const props = withDefaults(defineProps<{
-  form: Record<string, any>
-  filters?: FilterConfig[]
-  showDateRange?: boolean
-  placeholder?: string
-}>(), {
-  filters: () => [],
-  showDateRange: true,
-  placeholder: '搜索'
-})
+const props = withDefaults(
+  defineProps<{
+    form: Record<string, any>;
+    filters?: FilterConfig[];
+    showDateRange?: boolean;
+    placeholder?: string;
+  }>(),
+  {
+    filters: () => [],
+    showDateRange: true,
+    placeholder: '搜索'
+  }
+);
 
 const emit = defineEmits<{
-  search: []
-  reset: []
-}>()
+  search: [];
+  reset: [];
+}>();
 
 const handleSearch = () => {
-  emit('search')
-}
+  emit('search');
+};
 
 const handleReset = () => {
   // 重置表单
-  Object.keys(props.form).forEach(key => {
+  Object.keys(props.form).forEach((key) => {
     if (key === 'dateRange') {
-      props.form[key] = []
+      props.form[key] = [];
     } else {
-      props.form[key] = ''
+      props.form[key] = '';
     }
-  })
-  emit('reset')
-}
+  });
+  emit('reset');
+};
 </script>
 
 <style scoped lang="scss">
 .search-bar {
   margin-bottom: 20px;
-  
+
   .search-row {
     display: flex;
     align-items: center;
     gap: 16px;
     margin-bottom: 12px;
-    
+
     &:last-child {
       margin-bottom: 0;
     }
-    
+
     &.btn-only {
       margin-top: 0;
     }
   }
-  
+
   .filter-item {
     display: flex;
     align-items: center;
     gap: 8px;
-    
+
     .label {
       color: #666;
       font-size: 14px;
     }
   }
-  
+
   .btn-group {
     margin-left: auto;
     display: flex;

+ 7 - 7
src/components/StatCards/index.vue

@@ -9,13 +9,13 @@
 
 <script setup lang="ts">
 export interface StatItem {
-  value: string | number
-  label: string
+  value: string | number;
+  label: string;
 }
 
 defineProps<{
-  data: StatItem[]
-}>()
+  data: StatItem[];
+}>();
 </script>
 
 <style scoped lang="scss">
@@ -23,21 +23,21 @@ defineProps<{
   display: flex;
   gap: 20px;
   margin-bottom: 20px;
-  
+
   .stat-card {
     flex: 1;
     padding: 20px;
     background: #fafafa;
     border-radius: 4px;
     text-align: center;
-    
+
     .stat-value {
       font-size: 28px;
       font-weight: bold;
       color: #333;
       margin-bottom: 8px;
     }
-    
+
     .stat-label {
       font-size: 14px;
       color: #999;

+ 28 - 18
src/components/StatusTabs/index.vue

@@ -8,29 +8,29 @@
 </template>
 
 <script setup lang="ts">
-import type { Component } from 'vue'
+import type { Component } from 'vue';
 
 interface TabItem {
-  key: string
-  label: string
-  icon?: Component
+  key: string;
+  label: string;
+  icon?: Component;
 }
 
 defineProps<{
-  tabs: TabItem[]
-  modelValue: string
-  type?: 'line' | 'pill'  // line=下划线样式, pill=胶囊样式
-}>()
+  tabs: TabItem[];
+  modelValue: string;
+  type?: 'line' | 'pill'; // line=下划线样式, pill=胶囊样式
+}>();
 
 const emit = defineEmits<{
-  'update:modelValue': [value: string]
-  'change': [value: string]
-}>()
+  'update:modelValue': [value: string];
+  'change': [value: string];
+}>();
 
 const handleClick = (key: string) => {
-  emit('update:modelValue', key)
-  emit('change', key)
-}
+  emit('update:modelValue', key);
+  emit('change', key);
+};
 </script>
 
 <style scoped lang="scss">
@@ -53,8 +53,13 @@ const handleClick = (key: string) => {
       border-bottom: 2px solid transparent;
       margin-bottom: -1px;
       background: none;
-      &:hover, &.active { color: #333; }
-      &.active { border-bottom-color: #e60012; }
+      &:hover,
+      &.active {
+        color: #333;
+      }
+      &.active {
+        border-bottom-color: #e60012;
+      }
     }
   }
 
@@ -67,8 +72,13 @@ const handleClick = (key: string) => {
       color: #666;
       background: #f5f5f5;
       transition: all 0.2s;
-      &:hover { color: #e60012; }
-      &.active { background: #e60012; color: #fff; }
+      &:hover {
+        color: #e60012;
+      }
+      &.active {
+        background: #e60012;
+        color: #fff;
+      }
     }
   }
 }

+ 19 - 40
src/components/TableActions/index.vue

@@ -1,52 +1,31 @@
 <template>
   <div class="table-actions">
-    <el-button 
-      v-if="showEdit" 
-      type="primary" 
-      link 
-      size="small" 
-      @click="$emit('edit')"
-    >
-      编辑
-    </el-button>
-    <el-button 
-      v-if="showDelete" 
-      type="danger" 
-      link 
-      size="small" 
-      @click="$emit('delete')"
-    >
-      删除
-    </el-button>
-    <el-button 
-      v-if="showView" 
-      type="primary" 
-      link 
-      size="small" 
-      @click="$emit('view')"
-    >
-      查看
-    </el-button>
+    <el-button v-if="showEdit" type="primary" link size="small" @click="$emit('edit')"> 编辑 </el-button>
+    <el-button v-if="showDelete" type="danger" link size="small" @click="$emit('delete')"> 删除 </el-button>
+    <el-button v-if="showView" type="primary" link size="small" @click="$emit('view')"> 查看 </el-button>
     <slot></slot>
   </div>
 </template>
 
 <script setup lang="ts">
-withDefaults(defineProps<{
-  showEdit?: boolean
-  showDelete?: boolean
-  showView?: boolean
-}>(), {
-  showEdit: true,
-  showDelete: true,
-  showView: false
-})
+withDefaults(
+  defineProps<{
+    showEdit?: boolean;
+    showDelete?: boolean;
+    showView?: boolean;
+  }>(),
+  {
+    showEdit: true,
+    showDelete: true,
+    showView: false
+  }
+);
 
 defineEmits<{
-  edit: []
-  delete: []
-  view: []
-}>()
+  edit: [];
+  delete: [];
+  view: [];
+}>();
 </script>
 
 <style scoped lang="scss">

+ 6 - 6
src/components/TablePagination/index.vue

@@ -14,16 +14,16 @@
 </template>
 
 <script setup lang="ts">
-const page = defineModel<number>('page', { default: 1 })
-const pageSize = defineModel<number>('pageSize', { default: 10 })
+const page = defineModel<number>('page', { default: 1 });
+const pageSize = defineModel<number>('pageSize', { default: 10 });
 
 defineProps<{
-  total: number
-}>()
+  total: number;
+}>();
 
 defineEmits<{
-  change: []
-}>()
+  change: [];
+}>();
 </script>
 
 <style scoped lang="scss">

+ 11 - 22
src/components/index.ts

@@ -1,24 +1,13 @@
 // 公共组件统一导出
-import PageTitle from './PageTitle/index.vue'
-import StatusTabs from './StatusTabs/index.vue'
-import ProductItem from './ProductItem/index.vue'
-import ProductCard from './ProductCard/index.vue'
-import SearchBar from './SearchBar/index.vue'
-import StatCards from './StatCards/index.vue'
-import TablePagination from './TablePagination/index.vue'
-import HeaderBar from './HeaderBar/index.vue'
-import EmptyState from './EmptyState/index.vue'
-import TableActions from './TableActions/index.vue'
+import PageTitle from './PageTitle/index.vue';
+import StatusTabs from './StatusTabs/index.vue';
+import ProductItem from './ProductItem/index.vue';
+import ProductCard from './ProductCard/index.vue';
+import SearchBar from './SearchBar/index.vue';
+import StatCards from './StatCards/index.vue';
+import TablePagination from './TablePagination/index.vue';
+import HeaderBar from './HeaderBar/index.vue';
+import EmptyState from './EmptyState/index.vue';
+import TableActions from './TableActions/index.vue';
 
-export {
-  PageTitle,
-  StatusTabs,
-  ProductItem,
-  ProductCard,
-  SearchBar,
-  StatCards,
-  TablePagination,
-  HeaderBar,
-  EmptyState,
-  TableActions
-}
+export { PageTitle, StatusTabs, ProductItem, ProductCard, SearchBar, StatCards, TablePagination, HeaderBar, EmptyState, TableActions };

+ 6 - 6
src/constants/status.ts

@@ -3,39 +3,39 @@ export const BILL_STATUS_OPTIONS = [
   { label: '全部', value: '' },
   { label: '已对账', value: '1' },
   { label: '未对账', value: '0' }
-]
+];
 
 // 开票状态
 export const INVOICE_STATUS_OPTIONS = [
   { label: '全部', value: '' },
   { label: '已开票', value: '1' },
   { label: '未开票', value: '0' }
-]
+];
 
 // 支付状态
 export const PAY_STATUS_OPTIONS = [
   { label: '全部', value: '' },
   { label: '已支付', value: '1' },
   { label: '未支付', value: '0' }
-]
+];
 
 // 结算状态
 export const SETTLE_STATUS_OPTIONS = [
   { label: '全部', value: '' },
   { label: '已付款', value: '1' },
   { label: '待开票', value: '0' }
-]
+];
 
 // 订单状态样式映射
 export const ORDER_STATUS_CLASS: Record<string, string> = {
   '运输中': 'shipping',
   '待发货': 'pending',
   '已收货': 'received'
-}
+};
 
 // 开票状态样式映射
 export const INVOICE_STATUS_CLASS: Record<string, string> = {
   '已付款': 'paid',
   '已开票': 'invoiced',
   '待开票': 'pending-invoice'
-}
+};

+ 0 - 1
src/layout/components/breadcrumb.vue

@@ -27,7 +27,6 @@ watch(route, () => {
   meta.value = route.meta;
 });
 
-
 const goPath = (res: any) => {
   router.push(res.url);
 };

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

@@ -60,7 +60,6 @@ declare module 'vue' {
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTag: typeof import('element-plus/es')['ElTag']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
-
     ElTree: typeof import('element-plus/es')['ElTree']
     ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
     ElUpload: typeof import('element-plus/es')['ElUpload']

+ 0 - 1
src/utils/devtools-protection.ts

@@ -155,4 +155,3 @@ export function initDevToolsProtection(): void {
     lastHeight = currentHeight;
   });
 }
-

+ 8 - 0
src/utils/siteConfig.ts

@@ -52,6 +52,14 @@ export const SITE_ROUTES: Record<any, string[]> = {
     '/enterprise/companyInfo/edit',
     '/enterprise/purchaseHabit',
     '/enterprise/invoiceManage',
+    '/enterprise/agreementSupply',
+    '/enterprise/companyInfo',
+    '/enterprise/invoiceManage',
+    '/enterprise/myCollection',
+    '/enterprise/myFootprint',
+    '/enterprise/purchaseHabit',
+    '/enterprise/purchasePlan',
+    '/enterprise/purchaseHistory',
     '/reconciliation/billManage',
     '/reconciliation/invoiceManage',
     '/organization/deptManage',

+ 5 - 5
src/utils/status.ts

@@ -1,11 +1,11 @@
-import { ORDER_STATUS_CLASS, INVOICE_STATUS_CLASS } from '@/constants/status'
+import { ORDER_STATUS_CLASS, INVOICE_STATUS_CLASS } from '@/constants/status';
 
 // 获取订单状态样式类名
 export const getOrderStatusClass = (status: string): string => {
-  return ORDER_STATUS_CLASS[status] || ''
-}
+  return ORDER_STATUS_CLASS[status] || '';
+};
 
 // 获取开票状态样式类名
 export const getInvoiceStatusClass = (status: string): string => {
-  return INVOICE_STATUS_CLASS[status] || ''
-}
+  return INVOICE_STATUS_CLASS[status] || '';
+};

+ 20 - 16
src/views/analysis/deptPurchase/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="page-container">
     <PageTitle title="部门采购金额" />
-    
+
     <SearchBar :form="searchForm" :filters="filters" @search="handleSearch" @reset="handleReset">
       <template #buttons>
         <el-button type="danger">导出</el-button>
@@ -28,26 +28,30 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref } from 'vue'
-import { PageTitle, SearchBar, TablePagination } from '@/components'
+import { reactive, ref } from 'vue';
+import { PageTitle, SearchBar, TablePagination } from '@/components';
 
 const searchForm = reactive({
   keyword: '',
   dateRange: [],
   deptId: ''
-})
+});
 
 const filters = [
-  { field: 'deptId', label: '下单部门', options: [
-    { label: '全部', value: '' },
-    { label: '人事部', value: '1' },
-    { label: '行政部', value: '2' },
-    { label: '设计部', value: '3' },
-    { label: '技术部', value: '4' }
-  ]}
-]
+  {
+    field: 'deptId',
+    label: '下单部门',
+    options: [
+      { label: '全部', value: '' },
+      { label: '人事部', value: '1' },
+      { label: '行政部', value: '2' },
+      { label: '设计部', value: '3' },
+      { label: '技术部', value: '4' }
+    ]
+  }
+];
 
-const pagination = reactive({ page: 1, pageSize: 10, total: 100 })
+const pagination = reactive({ page: 1, pageSize: 10, total: 100 });
 
 const tableData = ref([
   { deptName: '人事部', orderPerson: '李世海', orderAmount: 2115.97, completedAmount: 2115.97, pendingAmount: 2115.97 },
@@ -60,13 +64,13 @@ const tableData = ref([
   { deptName: '行政部', orderPerson: '吴彦琛', orderAmount: 993.66, completedAmount: 993.66, pendingAmount: 993.66 },
   { deptName: '设计部', orderPerson: '钱慧倩', orderAmount: 2763.35, completedAmount: 2763.35, pendingAmount: 2763.35 },
   { deptName: '技术部', orderPerson: '孙银茹', orderAmount: 9286.45, completedAmount: 9286.45, pendingAmount: 9286.45 }
-])
+]);
 
 const handleSearch = () => {
   // 搜索逻辑
-}
+};
 
 const handleReset = () => {
   // 重置逻辑
-}
+};
 </script>

+ 143 - 56
src/views/analysis/orderAnalysis/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="page-container">
     <PageTitle title="订单交易分析" />
-    
+
     <StatCards :data="statData" />
 
     <!-- 采购金额趋势 -->
@@ -56,39 +56,39 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from 'vue'
-import * as echarts from 'echarts'
-import { PageTitle, StatCards } from '@/components'
+import { ref, onMounted } from 'vue';
+import * as echarts from 'echarts';
+import { PageTitle, StatCards } from '@/components';
 
-const trendType = ref('month')
-const categoryType = ref('month')
+const trendType = ref('month');
+const categoryType = ref('month');
 
-const trendChartRef = ref<HTMLElement>()
-const pieChartRef = ref<HTMLElement>()
-const dailyChartRef = ref<HTMLElement>()
+const trendChartRef = ref<HTMLElement>();
+const pieChartRef = ref<HTMLElement>();
+const dailyChartRef = ref<HTMLElement>();
 
 const statData = [
   { value: '10,239.00', label: '购买金额(元)' },
   { value: '3', label: '购买数量(件)' },
   { value: '45', label: '订单量(单)' },
   { value: '78', label: '客单价(元/单)' }
-]
+];
 
 const categoryData = [
   { name: '品类01', value: 600 },
   { name: '品类02', value: 200 },
   { name: '品类03', value: 200 }
-]
+];
 
 onMounted(() => {
-  initTrendChart()
-  initPieChart()
-  initDailyChart()
-})
+  initTrendChart();
+  initPieChart();
+  initDailyChart();
+});
 
 const initTrendChart = () => {
-  if (!trendChartRef.value) return
-  const chart = echarts.init(trendChartRef.value)
+  if (!trendChartRef.value) return;
+  const chart = echarts.init(trendChartRef.value);
   chart.setOption({
     grid: { left: 60, right: 20, top: 30, bottom: 30 },
     xAxis: {
@@ -98,45 +98,90 @@ const initTrendChart = () => {
       axisLabel: { color: '#999' }
     },
     yAxis: {
-      type: 'value', name: '销量(万元)', nameTextStyle: { color: '#999' },
-      axisLine: { show: false }, splitLine: { lineStyle: { color: '#eee' } }, axisLabel: { color: '#999' }
+      type: 'value',
+      name: '销量(万元)',
+      nameTextStyle: { color: '#999' },
+      axisLine: { show: false },
+      splitLine: { lineStyle: { color: '#eee' } },
+      axisLabel: { color: '#999' }
     },
-    series: [{ type: 'line', data: [300, 980, 450, 600, 400, 550, 900, 700, 650, 800, 500, 750], lineStyle: { color: '#e60012', width: 2 }, itemStyle: { color: '#e60012' }, symbol: 'circle', symbolSize: 6 }],
+    series: [
+      {
+        type: 'line',
+        data: [300, 980, 450, 600, 400, 550, 900, 700, 650, 800, 500, 750],
+        lineStyle: { color: '#e60012', width: 2 },
+        itemStyle: { color: '#e60012' },
+        symbol: 'circle',
+        symbolSize: 6
+      }
+    ],
     tooltip: { trigger: 'axis', formatter: (params: any) => `${params[0].name}<br/>¥${params[0].value.toFixed(2)}` }
-  })
-}
+  });
+};
 
 const initPieChart = () => {
-  if (!pieChartRef.value) return
-  const chart = echarts.init(pieChartRef.value)
+  if (!pieChartRef.value) return;
+  const chart = echarts.init(pieChartRef.value);
   chart.setOption({
-    series: [{
-      type: 'pie', radius: ['40%', '70%'], center: ['50%', '50%'],
-      data: [
-        { value: 600, name: '品类01', itemStyle: { color: '#f4c542' } },
-        { value: 200, name: '品类02', itemStyle: { color: '#3498db' } },
-        { value: 200, name: '品类03', itemStyle: { color: '#1abc9c' } }
-      ],
-      label: { formatter: '{b} {d}%', color: '#666' }
-    }],
+    series: [
+      {
+        type: 'pie',
+        radius: ['40%', '70%'],
+        center: ['50%', '50%'],
+        data: [
+          { value: 600, name: '品类01', itemStyle: { color: '#f4c542' } },
+          { value: 200, name: '品类02', itemStyle: { color: '#3498db' } },
+          { value: 200, name: '品类03', itemStyle: { color: '#1abc9c' } }
+        ],
+        label: { formatter: '{b} {d}%', color: '#666' }
+      }
+    ],
     tooltip: { formatter: '{b}: {c} ({d}%)' }
-  })
-}
+  });
+};
 
 const initDailyChart = () => {
-  if (!dailyChartRef.value) return
-  const chart = echarts.init(dailyChartRef.value)
+  if (!dailyChartRef.value) return;
+  const chart = echarts.init(dailyChartRef.value);
   chart.setOption({
     grid: { left: 60, right: 20, top: 40, bottom: 30 },
-    xAxis: { type: 'category', data: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], axisLine: { lineStyle: { color: '#eee' } }, axisLabel: { color: '#999' } },
-    yAxis: { type: 'value', name: '销量(万元)', nameTextStyle: { color: '#999' }, axisLine: { show: false }, splitLine: { lineStyle: { color: '#eee' } }, axisLabel: { color: '#999' } },
+    xAxis: {
+      type: 'category',
+      data: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
+      axisLine: { lineStyle: { color: '#eee' } },
+      axisLabel: { color: '#999' }
+    },
+    yAxis: {
+      type: 'value',
+      name: '销量(万元)',
+      nameTextStyle: { color: '#999' },
+      axisLine: { show: false },
+      splitLine: { lineStyle: { color: '#eee' } },
+      axisLabel: { color: '#999' }
+    },
     series: [
-      { name: '购买金额', type: 'line', data: [400, 500, 350, 600, 450, 640, 500, 550, 400, 600, 450, 500], lineStyle: { color: '#f4c542', width: 2 }, itemStyle: { color: '#f4c542' }, symbol: 'circle', symbolSize: 6 },
-      { name: '购买数量', type: 'line', data: [300, 400, 250, 500, 350, 332, 400, 450, 300, 500, 350, 400], lineStyle: { color: '#3498db', width: 2 }, itemStyle: { color: '#3498db' }, symbol: 'circle', symbolSize: 6 }
+      {
+        name: '购买金额',
+        type: 'line',
+        data: [400, 500, 350, 600, 450, 640, 500, 550, 400, 600, 450, 500],
+        lineStyle: { color: '#f4c542', width: 2 },
+        itemStyle: { color: '#f4c542' },
+        symbol: 'circle',
+        symbolSize: 6
+      },
+      {
+        name: '购买数量',
+        type: 'line',
+        data: [300, 400, 250, 500, 350, 332, 400, 450, 300, 500, 350, 400],
+        lineStyle: { color: '#3498db', width: 2 },
+        itemStyle: { color: '#3498db' },
+        symbol: 'circle',
+        symbolSize: 6
+      }
     ],
     tooltip: { trigger: 'axis' }
-  })
-}
+  });
+};
 </script>
 
 <style scoped lang="scss">
@@ -146,20 +191,34 @@ const initDailyChart = () => {
   border-radius: 4px;
   padding: 16px;
   margin-bottom: 20px;
-  
-  &.half { flex: 1; margin-bottom: 0; }
-  
+
+  &.half {
+    flex: 1;
+    margin-bottom: 0;
+  }
+
   .chart-header {
     display: flex;
     align-items: center;
     gap: 16px;
     margin-bottom: 16px;
-    
-    .chart-title { font-size: 14px; font-weight: 500; color: #333; }
+
+    .chart-title {
+      font-size: 14px;
+      font-weight: 500;
+      color: #333;
+    }
     .chart-filter {
       display: flex;
       gap: 12px;
-      span { font-size: 12px; color: #999; cursor: pointer; &.active { color: #333; } }
+      span {
+        font-size: 12px;
+        color: #999;
+        cursor: pointer;
+        &.active {
+          color: #333;
+        }
+      }
     }
     .chart-legend {
       margin-left: auto;
@@ -171,13 +230,29 @@ const initDailyChart = () => {
         gap: 4px;
         font-size: 12px;
         color: #666;
-        .dot { width: 8px; height: 8px; border-radius: 50%; &.yellow { background: #f4c542; } &.blue { background: #3498db; } }
+        .dot {
+          width: 8px;
+          height: 8px;
+          border-radius: 50%;
+          &.yellow {
+            background: #f4c542;
+          }
+          &.blue {
+            background: #3498db;
+          }
+        }
       }
     }
   }
-  
-  .chart-container { height: 280px; &.pie { height: 200px; width: 200px; } }
-  
+
+  .chart-container {
+    height: 280px;
+    &.pie {
+      height: 200px;
+      width: 200px;
+    }
+  }
+
   .chart-content-row {
     display: flex;
     align-items: center;
@@ -188,12 +263,24 @@ const initDailyChart = () => {
       padding-left: 20px;
       .category-item {
         text-align: center;
-        .category-name { display: block; font-size: 12px; color: #999; margin-bottom: 8px; }
-        .category-value { font-size: 20px; font-weight: bold; color: #333; }
+        .category-name {
+          display: block;
+          font-size: 12px;
+          color: #999;
+          margin-bottom: 8px;
+        }
+        .category-value {
+          font-size: 20px;
+          font-weight: bold;
+          color: #333;
+        }
       }
     }
   }
 }
 
-.chart-row { display: flex; gap: 20px; }
+.chart-row {
+  display: flex;
+  gap: 20px;
+}
 </style>

+ 119 - 19
src/views/analysis/orderStatus/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="page-container">
     <PageTitle title="订单执行状态" />
-    
+
     <SearchBar :form="searchForm" :filters="filters" />
 
     <el-table :data="tableData" border style="width: 100%">
@@ -29,10 +29,10 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref } from 'vue'
-import { PageTitle, SearchBar, TablePagination } from '@/components'
-import { BILL_STATUS_OPTIONS, INVOICE_STATUS_OPTIONS, PAY_STATUS_OPTIONS } from '@/constants/status'
-import { getOrderStatusClass } from '@/utils/status'
+import { reactive, ref } from 'vue';
+import { PageTitle, SearchBar, TablePagination } from '@/components';
+import { BILL_STATUS_OPTIONS, INVOICE_STATUS_OPTIONS, PAY_STATUS_OPTIONS } from '@/constants/status';
+import { getOrderStatusClass } from '@/utils/status';
 
 const searchForm = reactive({
   keyword: '',
@@ -40,26 +40,126 @@ const searchForm = reactive({
   billStatus: '',
   invoiceStatus: '',
   payStatus: ''
-})
+});
 
 const filters = [
   { field: 'billStatus', label: '对账状态', options: BILL_STATUS_OPTIONS },
   { field: 'invoiceStatus', label: '开票状态', options: INVOICE_STATUS_OPTIONS },
   { field: 'payStatus', label: '支付状态', options: PAY_STATUS_OPTIONS }
-]
+];
 
-const pagination = reactive({ page: 1, pageSize: 10, total: 100 })
+const pagination = reactive({ page: 1, pageSize: 10, total: 100 });
 
 const tableData = ref([
-  { orderDate: '2025-11-22', orderNo: '2323232323', productName: '清华同方超...', quantity: 2, price: 2115.97, amount: 2115.97, shippedQty: 2, unshippedQty: 2, status: '运输中' },
-  { orderDate: '2025-12-09', orderNo: '2323412123', productName: '清华同方超...', quantity: 1, price: 4476.12, amount: 4476.12, shippedQty: 1, unshippedQty: 1, status: '待发货' },
-  { orderDate: '2025-11-21', orderNo: '4566784543', productName: '越E500台式...', quantity: 5, price: 7751.34, amount: 7751.34, shippedQty: 5, unshippedQty: 5, status: '已收货' },
-  { orderDate: '2025-11-27', orderNo: '2356565654', productName: '清华同机电...', quantity: 2, price: 7936.37, amount: 7936.37, shippedQty: 2, unshippedQty: 2, status: '运输中' },
-  { orderDate: '2025-12-11', orderNo: '2323412123', productName: '清华同方超...', quantity: 1, price: 3931.45, amount: 3931.45, shippedQty: 1, unshippedQty: 1, status: '待发货' },
-  { orderDate: '2025-11-27', orderNo: '2323232323', productName: '清00台式机...', quantity: 6, price: 6132.75, amount: 6132.75, shippedQty: 6, unshippedQty: 6, status: '已收货' },
-  { orderDate: '2025-11-25', orderNo: '2356565654', productName: '机电脑超越...', quantity: 7, price: 3189.56, amount: 3189.56, shippedQty: 7, unshippedQty: 7, status: '已收货' },
-  { orderDate: '2025-11-20', orderNo: '2323232323', productName: '清华同方超...', quantity: 2, price: 993.66, amount: 993.66, shippedQty: 2, unshippedQty: 2, status: '运输中' },
-  { orderDate: '2025-11-17', orderNo: '2323412123', productName: '超越E500台...', quantity: 1, price: 2763.35, amount: 2763.35, shippedQty: 1, unshippedQty: 1, status: '待发货' },
-  { orderDate: '2025-12-05', orderNo: '2323232323', productName: '机电脑超越...', quantity: 5, price: 9286.45, amount: 9286.45, shippedQty: 5, unshippedQty: 5, status: '已收货' }
-])
+  {
+    orderDate: '2025-11-22',
+    orderNo: '2323232323',
+    productName: '清华同方超...',
+    quantity: 2,
+    price: 2115.97,
+    amount: 2115.97,
+    shippedQty: 2,
+    unshippedQty: 2,
+    status: '运输中'
+  },
+  {
+    orderDate: '2025-12-09',
+    orderNo: '2323412123',
+    productName: '清华同方超...',
+    quantity: 1,
+    price: 4476.12,
+    amount: 4476.12,
+    shippedQty: 1,
+    unshippedQty: 1,
+    status: '待发货'
+  },
+  {
+    orderDate: '2025-11-21',
+    orderNo: '4566784543',
+    productName: '越E500台式...',
+    quantity: 5,
+    price: 7751.34,
+    amount: 7751.34,
+    shippedQty: 5,
+    unshippedQty: 5,
+    status: '已收货'
+  },
+  {
+    orderDate: '2025-11-27',
+    orderNo: '2356565654',
+    productName: '清华同机电...',
+    quantity: 2,
+    price: 7936.37,
+    amount: 7936.37,
+    shippedQty: 2,
+    unshippedQty: 2,
+    status: '运输中'
+  },
+  {
+    orderDate: '2025-12-11',
+    orderNo: '2323412123',
+    productName: '清华同方超...',
+    quantity: 1,
+    price: 3931.45,
+    amount: 3931.45,
+    shippedQty: 1,
+    unshippedQty: 1,
+    status: '待发货'
+  },
+  {
+    orderDate: '2025-11-27',
+    orderNo: '2323232323',
+    productName: '清00台式机...',
+    quantity: 6,
+    price: 6132.75,
+    amount: 6132.75,
+    shippedQty: 6,
+    unshippedQty: 6,
+    status: '已收货'
+  },
+  {
+    orderDate: '2025-11-25',
+    orderNo: '2356565654',
+    productName: '机电脑超越...',
+    quantity: 7,
+    price: 3189.56,
+    amount: 3189.56,
+    shippedQty: 7,
+    unshippedQty: 7,
+    status: '已收货'
+  },
+  {
+    orderDate: '2025-11-20',
+    orderNo: '2323232323',
+    productName: '清华同方超...',
+    quantity: 2,
+    price: 993.66,
+    amount: 993.66,
+    shippedQty: 2,
+    unshippedQty: 2,
+    status: '运输中'
+  },
+  {
+    orderDate: '2025-11-17',
+    orderNo: '2323412123',
+    productName: '超越E500台...',
+    quantity: 1,
+    price: 2763.35,
+    amount: 2763.35,
+    shippedQty: 1,
+    unshippedQty: 1,
+    status: '待发货'
+  },
+  {
+    orderDate: '2025-12-05',
+    orderNo: '2323232323',
+    productName: '机电脑超越...',
+    quantity: 5,
+    price: 9286.45,
+    amount: 9286.45,
+    shippedQty: 5,
+    unshippedQty: 5,
+    status: '已收货'
+  }
+]);
 </script>

+ 60 - 32
src/views/analysis/purchaseDetail/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="page-container">
     <PageTitle title="商品采购明细" />
-    
+
     <StatCards :data="statData" />
 
     <!-- 采购效率分析 -->
@@ -12,7 +12,7 @@
           <div class="card-label">{{ item.label }}</div>
           <div class="card-content">
             <span class="card-value">{{ item.value }}</span>
-            <div class="mini-chart" :ref="el => setChartRef(el, index)"></div>
+            <div class="mini-chart" :ref="(el) => setChartRef(el, index)"></div>
           </div>
         </div>
       </div>
@@ -26,7 +26,7 @@
           <div class="card-label">{{ item.label }}</div>
           <div class="card-content">
             <span class="card-value">{{ item.value }}</span>
-            <div class="mini-chart" :ref="el => setChartRef(el, index + 3)"></div>
+            <div class="mini-chart" :ref="(el) => setChartRef(el, index + 3)"></div>
           </div>
         </div>
       </div>
@@ -35,86 +35,114 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from 'vue'
-import * as echarts from 'echarts'
-import { PageTitle, StatCards } from '@/components'
+import { ref, onMounted } from 'vue';
+import * as echarts from 'echarts';
+import { PageTitle, StatCards } from '@/components';
 
-const chartRefs = ref<(HTMLElement | null)[]>([])
+const chartRefs = ref<(HTMLElement | null)[]>([]);
 
 const setChartRef = (el: any, index: number) => {
-  if (el) chartRefs.value[index] = el as HTMLElement
-}
+  if (el) chartRefs.value[index] = el as HTMLElement;
+};
 
 const statData = [
   { value: '12', label: '购买数量(件)' },
   { value: '3', label: '商品数量(件)' },
   { value: '45', label: '品牌数量(件)' },
   { value: '78', label: '三类品类数量(件)' }
-]
+];
 
 const efficiencyData = [
   { label: '订单平均确认时间(小时)', value: '0.50', data: [30, 40, 35, 50, 45, 60, 55, 70, 65, 80], color: '#e60012', type: 'line' },
   { label: '商品平均出库时间(小时)', value: '7', data: [3, 5, 4, 6, 5, 7, 6, 8, 7, 9, 8, 7], color: '#3498db', type: 'bar' },
   { label: '商品平均妥投时间(小时)', value: '16.5', data: [40, 50, 45, 60, 55, 70, 65, 80, 75, 90], color: '#f4c542', type: 'line' }
-]
+];
 
 const afterSaleData = [
   { label: '售后商品数量(件)', value: '23', data: [20, 30, 25, 40, 35, 50, 45, 60, 55, 70], color: '#e60012', type: 'line' },
   { label: '平均完成时效(天)', value: '7', data: [2, 4, 3, 5, 4, 6, 5, 7, 6, 8, 7, 6], color: '#3498db', type: 'bar' },
   { label: '售后商品占比', value: '16.5%', data: [10, 15, 12, 18, 14, 20, 16, 22, 18, 25], color: '#f4c542', type: 'line' }
-]
+];
 
 onMounted(() => {
-  const allData = [...efficiencyData, ...afterSaleData]
+  const allData = [...efficiencyData, ...afterSaleData];
   allData.forEach((item, index) => {
-    const el = chartRefs.value[index]
-    if (!el) return
-    const chart = echarts.init(el)
+    const el = chartRefs.value[index];
+    if (!el) return;
+    const chart = echarts.init(el);
     if (item.type === 'line') {
       chart.setOption({
         grid: { left: 0, right: 0, top: 5, bottom: 5 },
         xAxis: { type: 'category', show: false, data: item.data.map((_, i) => i) },
         yAxis: { type: 'value', show: false },
-        series: [{
-          type: 'line', data: item.data, lineStyle: { color: item.color, width: 2 }, itemStyle: { color: item.color }, symbol: 'none', smooth: true,
-          areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: item.color + '40' }, { offset: 1, color: item.color + '10' }]) }
-        }]
-      })
+        series: [
+          {
+            type: 'line',
+            data: item.data,
+            lineStyle: { color: item.color, width: 2 },
+            itemStyle: { color: item.color },
+            symbol: 'none',
+            smooth: true,
+            areaStyle: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 0, color: item.color + '40' },
+                { offset: 1, color: item.color + '10' }
+              ])
+            }
+          }
+        ]
+      });
     } else {
       chart.setOption({
         grid: { left: 0, right: 0, top: 5, bottom: 5 },
         xAxis: { type: 'category', show: false, data: item.data.map((_, i) => i) },
         yAxis: { type: 'value', show: false },
         series: [{ type: 'bar', data: item.data, itemStyle: { color: item.color }, barWidth: 4 }]
-      })
+      });
     }
-  })
-})
+  });
+});
 </script>
 
 <style scoped lang="scss">
 .analysis-section {
   margin-bottom: 24px;
-  
-  .section-title { font-size: 14px; font-weight: 500; color: #333; margin-bottom: 16px; }
-  
+
+  .section-title {
+    font-size: 14px;
+    font-weight: 500;
+    color: #333;
+    margin-bottom: 16px;
+  }
+
   .analysis-cards {
     display: flex;
     gap: 20px;
-    
+
     .analysis-card {
       flex: 1;
       padding: 16px;
       border: 1px solid #eee;
       border-radius: 4px;
-      
-      .card-label { font-size: 12px; color: #999; margin-bottom: 12px; }
+
+      .card-label {
+        font-size: 12px;
+        color: #999;
+        margin-bottom: 12px;
+      }
       .card-content {
         display: flex;
         align-items: flex-end;
         justify-content: space-between;
-        .card-value { font-size: 32px; font-weight: bold; color: #333; }
-        .mini-chart { width: 120px; height: 50px; }
+        .card-value {
+          font-size: 32px;
+          font-weight: bold;
+          color: #333;
+        }
+        .mini-chart {
+          width: 120px;
+          height: 50px;
+        }
       }
     }
   }

+ 109 - 19
src/views/analysis/settlementStatus/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="page-container">
     <PageTitle title="对账结算状况" />
-    
+
     <SearchBar :form="searchForm" :filters="filters">
       <template #buttons>
         <el-button type="danger">导出</el-button>
@@ -42,10 +42,10 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref } from 'vue'
-import { PageTitle, SearchBar, TablePagination } from '@/components'
-import { BILL_STATUS_OPTIONS, INVOICE_STATUS_OPTIONS, SETTLE_STATUS_OPTIONS } from '@/constants/status'
-import { getOrderStatusClass, getInvoiceStatusClass } from '@/utils/status'
+import { reactive, ref } from 'vue';
+import { PageTitle, SearchBar, TablePagination } from '@/components';
+import { BILL_STATUS_OPTIONS, INVOICE_STATUS_OPTIONS, SETTLE_STATUS_OPTIONS } from '@/constants/status';
+import { getOrderStatusClass, getInvoiceStatusClass } from '@/utils/status';
 
 const searchForm = reactive({
   keyword: '',
@@ -53,26 +53,116 @@ const searchForm = reactive({
   billStatus: '',
   invoiceStatus: '',
   settleStatus: ''
-})
+});
 
 const filters = [
   { field: 'billStatus', label: '对账状态', options: BILL_STATUS_OPTIONS },
   { field: 'invoiceStatus', label: '开票状态', options: INVOICE_STATUS_OPTIONS },
   { field: 'settleStatus', label: '结算状态', options: SETTLE_STATUS_OPTIONS }
-]
+];
 
-const pagination = reactive({ page: 1, pageSize: 10, total: 100 })
+const pagination = reactive({ page: 1, pageSize: 10, total: 100 });
 
 const tableData = ref([
-  { orderNo: '232323232', orderType: '普通订单', amount: 2115.97, orderDate: '2025-11-22', orderStatus: '运输中', billStatus: '已对账', invoiceStatus: '已付款', settleStatus: '已结算' },
-  { orderNo: '2323412123', orderType: '普通订单', amount: 4476.12, orderDate: '2025-12-09', orderStatus: '待发货', billStatus: '未对账', invoiceStatus: '待开票', settleStatus: '未结算' },
-  { orderNo: '4566784543', orderType: '协议订单', amount: 7751.34, orderDate: '2025-11-21', orderStatus: '已收货', billStatus: '已对账', invoiceStatus: '已开票', settleStatus: '已结算' },
-  { orderNo: '2356565654', orderType: '普通订单', amount: 7936.37, orderDate: '2025-11-27', orderStatus: '运输中', billStatus: '未对账', invoiceStatus: '已付款', settleStatus: '未结算' },
-  { orderNo: '2323412123', orderType: '协议订单', amount: 3931.45, orderDate: '2025-12-11', orderStatus: '待发货', billStatus: '未对账', invoiceStatus: '待开票', settleStatus: '未结算' },
-  { orderNo: '2323232323', orderType: '普通订单', amount: 6132.75, orderDate: '2025-11-27', orderStatus: '已收货', billStatus: '已对账', invoiceStatus: '已开票', settleStatus: '已结算' },
-  { orderNo: '2356565654', orderType: '普通订单', amount: 3189.56, orderDate: '2025-11-25', orderStatus: '已收货', billStatus: '已对账', invoiceStatus: '已开票', settleStatus: '已结算' },
-  { orderNo: '2323232323', orderType: '协议订单', amount: 993.66, orderDate: '2025-11-20', orderStatus: '运输中', billStatus: '未对账', invoiceStatus: '已付款', settleStatus: '未结算' },
-  { orderNo: '2323412123', orderType: '普通订单', amount: 2763.35, orderDate: '2025-11-17', orderStatus: '待发货', billStatus: '未对账', invoiceStatus: '待开票', settleStatus: '未结算' },
-  { orderNo: '2323232323', orderType: '协议订单', amount: 9286.45, orderDate: '2025-12-05', orderStatus: '已收货', billStatus: '已对账', invoiceStatus: '已开票', settleStatus: '已结算' }
-])
+  {
+    orderNo: '232323232',
+    orderType: '普通订单',
+    amount: 2115.97,
+    orderDate: '2025-11-22',
+    orderStatus: '运输中',
+    billStatus: '已对账',
+    invoiceStatus: '已付款',
+    settleStatus: '已结算'
+  },
+  {
+    orderNo: '2323412123',
+    orderType: '普通订单',
+    amount: 4476.12,
+    orderDate: '2025-12-09',
+    orderStatus: '待发货',
+    billStatus: '未对账',
+    invoiceStatus: '待开票',
+    settleStatus: '未结算'
+  },
+  {
+    orderNo: '4566784543',
+    orderType: '协议订单',
+    amount: 7751.34,
+    orderDate: '2025-11-21',
+    orderStatus: '已收货',
+    billStatus: '已对账',
+    invoiceStatus: '已开票',
+    settleStatus: '已结算'
+  },
+  {
+    orderNo: '2356565654',
+    orderType: '普通订单',
+    amount: 7936.37,
+    orderDate: '2025-11-27',
+    orderStatus: '运输中',
+    billStatus: '未对账',
+    invoiceStatus: '已付款',
+    settleStatus: '未结算'
+  },
+  {
+    orderNo: '2323412123',
+    orderType: '协议订单',
+    amount: 3931.45,
+    orderDate: '2025-12-11',
+    orderStatus: '待发货',
+    billStatus: '未对账',
+    invoiceStatus: '待开票',
+    settleStatus: '未结算'
+  },
+  {
+    orderNo: '2323232323',
+    orderType: '普通订单',
+    amount: 6132.75,
+    orderDate: '2025-11-27',
+    orderStatus: '已收货',
+    billStatus: '已对账',
+    invoiceStatus: '已开票',
+    settleStatus: '已结算'
+  },
+  {
+    orderNo: '2356565654',
+    orderType: '普通订单',
+    amount: 3189.56,
+    orderDate: '2025-11-25',
+    orderStatus: '已收货',
+    billStatus: '已对账',
+    invoiceStatus: '已开票',
+    settleStatus: '已结算'
+  },
+  {
+    orderNo: '2323232323',
+    orderType: '协议订单',
+    amount: 993.66,
+    orderDate: '2025-11-20',
+    orderStatus: '运输中',
+    billStatus: '未对账',
+    invoiceStatus: '已付款',
+    settleStatus: '未结算'
+  },
+  {
+    orderNo: '2323412123',
+    orderType: '普通订单',
+    amount: 2763.35,
+    orderDate: '2025-11-17',
+    orderStatus: '待发货',
+    billStatus: '未对账',
+    invoiceStatus: '待开票',
+    settleStatus: '未结算'
+  },
+  {
+    orderNo: '2323232323',
+    orderType: '协议订单',
+    amount: 9286.45,
+    orderDate: '2025-12-05',
+    orderStatus: '已收货',
+    billStatus: '已对账',
+    invoiceStatus: '已开票',
+    settleStatus: '已结算'
+  }
+]);
 </script>

+ 157 - 25
src/views/enterprise/messageNotice/index.vue

@@ -34,56 +34,188 @@
     </div>
 
     <!-- 分页 -->
-    <TablePagination v-if="messageList.length > 0" v-model:page="queryParams.pageNum" v-model:pageSize="queryParams.pageSize" :total="total" @change="handleQuery" />
+    <TablePagination
+      v-if="messageList.length > 0"
+      v-model:page="queryParams.pageNum"
+      v-model:pageSize="queryParams.pageSize"
+      :total="total"
+      @change="handleQuery"
+    />
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch } from 'vue'
-import { Document, Bell, Warning, User, Box } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
-import { PageTitle, TablePagination } from '@/components'
+import { ref, reactive, watch } from 'vue';
+import { Document, Bell, Warning, User, Box } from '@element-plus/icons-vue';
+import { ElMessage } from 'element-plus';
+import { PageTitle, TablePagination } from '@/components';
 
-const activeTab = ref('approval')
+const activeTab = ref('approval');
 const tabs = [
   { key: 'approval', label: '审批待办', icon: Document },
   { key: 'arrival', label: '到货提醒', icon: Bell },
   { key: 'budget', label: '预算预警', icon: Warning }
-]
+];
 
-const queryParams = reactive({ pageNum: 1, pageSize: 10 })
-const total = ref(0)
-const messageList = ref<any[]>([])
+const queryParams = reactive({ pageNum: 1, pageSize: 10 });
+const total = ref(0);
+const messageList = ref<any[]>([]);
 
 const loadData = () => {
   if (activeTab.value === 'approval') {
     messageList.value = [
-      { time: '2025/01/10 11:42:32', title: '您有一个审批流程待处理', desc: '审批名称:办公用品采购申请', unread: true, showAction: true, iconType: 'user' },
-      { time: '2025/01/10 11:42:32', title: '您的办公用品审批申请已通过', desc: '审批名称:办公用品采购申请', unread: true, showAction: false, iconType: 'user' },
-      { time: '2025/01/10 11:42:32', title: '您的办公用品审批申请已通过', desc: '审批名称:办公用品采购申请', unread: true, showAction: false, iconType: 'user' }
-    ]
-    total.value = 3
+      {
+        time: '2025/01/10 11:42:32',
+        title: '您有一个审批流程待处理',
+        desc: '审批名称:办公用品采购申请',
+        unread: true,
+        showAction: true,
+        iconType: 'user'
+      },
+      {
+        time: '2025/01/10 11:42:32',
+        title: '您的办公用品审批申请已通过',
+        desc: '审批名称:办公用品采购申请',
+        unread: true,
+        showAction: false,
+        iconType: 'user'
+      },
+      {
+        time: '2025/01/10 11:42:32',
+        title: '您的办公用品审批申请已通过',
+        desc: '审批名称:办公用品采购申请',
+        unread: true,
+        showAction: false,
+        iconType: 'user'
+      }
+    ];
+    total.value = 3;
   } else if (activeTab.value === 'arrival') {
     messageList.value = [
       { time: '2025/01/10 11:42:32', title: '包裹到货提醒', desc: '物流状态:已到达代售点', unread: true, showAction: false, iconType: 'package' },
       { time: '2025/01/10 11:42:32', title: '包裹到货提醒', desc: '物流状态:已到达代售点', unread: false, showAction: false, iconType: 'package' }
-    ]
-    total.value = 2
+    ];
+    total.value = 2;
   } else {
     messageList.value = [
       { time: '2025/01/10 11:42:32', title: '预算预警', desc: '副标题副副标题', unread: true, showAction: false, iconType: 'budget' },
       { time: '2025/01/10 11:42:32', title: '预算预警', desc: '副标题副副标题', unread: false, showAction: false, iconType: 'budget' }
-    ]
-    total.value = 2
+    ];
+    total.value = 2;
   }
-}
+};
 
-watch(activeTab, () => { queryParams.pageNum = 1; loadData() }, { immediate: true })
-const handleQuery = () => { loadData() }
-const handleProcess = (_item: any) => { ElMessage.info('跳转到审批处理页面') }
+watch(
+  activeTab,
+  () => {
+    queryParams.pageNum = 1;
+    loadData();
+  },
+  { immediate: true }
+);
+const handleQuery = () => {
+  loadData();
+};
+const handleProcess = (_item: any) => {
+  ElMessage.info('跳转到审批处理页面');
+};
 </script>
 
 <style scoped lang="scss">
-.tab-nav { display: flex; gap: 30px; border-bottom: 1px solid #eee; margin-bottom: 20px; .tab-item { display: flex; align-items: center; gap: 5px; padding: 10px 0; cursor: pointer; color: #666; font-size: 14px; border-bottom: 2px solid transparent; margin-bottom: -1px; &:hover, &.active { color: #333; } &.active { border-bottom-color: #e60012; } } }
-.message-list { .message-item { padding: 15px 0; border-bottom: 1px solid #f5f5f5; &:last-child { border-bottom: none; } .message-content { display: flex; align-items: center; gap: 15px; .message-icon { position: relative; width: 40px; height: 40px; border-radius: 8px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; &.user { background: #e60012; } &.package { background: #fff5f5; border: 1px solid #ffe0e0; } &.budget { background: #e60012; .budget-text { color: #fff; font-size: 16px; font-weight: bold; } } .unread-dot { position: absolute; top: -2px; right: -2px; width: 8px; height: 8px; border-radius: 50%; background: #e60012; border: 2px solid #fff; } } .message-info { flex: 1; .message-title { font-size: 14px; font-weight: 500; color: #333; margin-bottom: 5px; } .message-desc { font-size: 13px; color: #999; } } .message-right { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; .message-time { font-size: 12px; color: #999; } } } } }
+.tab-nav {
+  display: flex;
+  gap: 30px;
+  border-bottom: 1px solid #eee;
+  margin-bottom: 20px;
+  .tab-item {
+    display: flex;
+    align-items: center;
+    gap: 5px;
+    padding: 10px 0;
+    cursor: pointer;
+    color: #666;
+    font-size: 14px;
+    border-bottom: 2px solid transparent;
+    margin-bottom: -1px;
+    &:hover,
+    &.active {
+      color: #333;
+    }
+    &.active {
+      border-bottom-color: #e60012;
+    }
+  }
+}
+.message-list {
+  .message-item {
+    padding: 15px 0;
+    border-bottom: 1px solid #f5f5f5;
+    &:last-child {
+      border-bottom: none;
+    }
+    .message-content {
+      display: flex;
+      align-items: center;
+      gap: 15px;
+      .message-icon {
+        position: relative;
+        width: 40px;
+        height: 40px;
+        border-radius: 8px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        flex-shrink: 0;
+        &.user {
+          background: #e60012;
+        }
+        &.package {
+          background: #fff5f5;
+          border: 1px solid #ffe0e0;
+        }
+        &.budget {
+          background: #e60012;
+          .budget-text {
+            color: #fff;
+            font-size: 16px;
+            font-weight: bold;
+          }
+        }
+        .unread-dot {
+          position: absolute;
+          top: -2px;
+          right: -2px;
+          width: 8px;
+          height: 8px;
+          border-radius: 50%;
+          background: #e60012;
+          border: 2px solid #fff;
+        }
+      }
+      .message-info {
+        flex: 1;
+        .message-title {
+          font-size: 14px;
+          font-weight: 500;
+          color: #333;
+          margin-bottom: 5px;
+        }
+        .message-desc {
+          font-size: 13px;
+          color: #999;
+        }
+      }
+      .message-right {
+        display: flex;
+        flex-direction: column;
+        align-items: flex-end;
+        gap: 8px;
+        .message-time {
+          font-size: 12px;
+          color: #999;
+        }
+      }
+    }
+  }
+}
 </style>

+ 114 - 77
src/views/enterprise/myFootprint/index.vue

@@ -2,81 +2,85 @@
   <div class="page-container">
     <div class="page-header">
       <PageTitle title="我的足迹" />
-      <el-button type="danger" link @click="handleClearAll"><el-icon><Delete /></el-icon>删除足迹</el-button>
+      <el-button type="danger" link @click="handleClearAll"
+        ><el-icon><Delete /></el-icon>删除足迹</el-button
+      >
     </div>
     <!-- 按日期分组的足迹列表 -->
     <div class="footprint-list">
       <div v-for="(group, groupIndex) in footprintGroups" :key="groupIndex" class="footprint-group">
-        <div class="group-header"><span class="group-date">{{ group.date }} {{ group.label }}</span></div>
+        <div class="group-header">
+          <span class="group-date">{{ group.date }} {{ group.label }}</span>
+        </div>
         <div class="product-grid">
-          <ProductCard
-            v-for="(item, itemIndex) in group.products"
-            :key="itemIndex"
-            :product="item"
-            v-model="item.checked"
-            show-checkbox
-          />
+          <ProductCard v-for="(item, itemIndex) in group.products" :key="itemIndex" :product="item" v-model="item.checked" show-checkbox />
         </div>
       </div>
     </div>
     <el-empty v-if="footprintGroups.length === 0" description="暂无浏览足迹" />
-    <TablePagination v-if="footprintGroups.length > 0" v-model:page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" :total="total" @change="handleQuery" />
+    <TablePagination
+      v-if="footprintGroups.length > 0"
+      v-model:page="queryParams.pageNum"
+      v-model:page-size="queryParams.pageSize"
+      :total="total"
+      @change="handleQuery"
+    />
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted } from 'vue'
-import { Delete } from '@element-plus/icons-vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { PageTitle, ProductCard, TablePagination } from '@/components'
-import { browsingHistoryList, deleteProductBrowsingHistory } from '@/api/goods/index'
+import { ref, reactive, onMounted } from 'vue';
+import { Delete } from '@element-plus/icons-vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { PageTitle, ProductCard, TablePagination } from '@/components';
+import { browsingHistoryList, deleteProductBrowsingHistory } from '@/api/goods/index';
 
-const queryParams = reactive({ pageNum: 1, pageSize: 20 })
-const total = ref(0)
-const loading = ref(false)
-const footprintGroups = ref<any[]>([])
+const queryParams = reactive({ pageNum: 1, pageSize: 20 });
+const total = ref(0);
+const loading = ref(false);
+const footprintGroups = ref<any[]>([]);
 
 /** 根据日期生成标签(今天/昨天/空) */
 const getDateLabel = (dateStr: string) => {
-  const today = new Date()
-  const target = new Date(dateStr)
-  today.setHours(0, 0, 0, 0)
-  target.setHours(0, 0, 0, 0)
-  const diff = today.getTime() - target.getTime()
-  const oneDay = 24 * 60 * 60 * 1000
-  if (diff === 0) return '今天'
-  if (diff === oneDay) return '昨天'
-  return ''
-}
+  const today = new Date();
+  const target = new Date(dateStr);
+  today.setHours(0, 0, 0, 0);
+  target.setHours(0, 0, 0, 0);
+  const diff = today.getTime() - target.getTime();
+  const oneDay = 24 * 60 * 60 * 1000;
+  if (diff === 0) return '今天';
+  if (diff === oneDay) return '昨天';
+  return '';
+};
 
 /** 格式化日期为 MM月DD日 */
 const formatDate = (dateStr: string) => {
-  const d = new Date(dateStr)
-  return `${d.getMonth() + 1}月${String(d.getDate()).padStart(2, '0')}日`
-}
+  const d = new Date(dateStr);
+  return `${d.getMonth() + 1}月${String(d.getDate()).padStart(2, '0')}日`;
+};
 
 /** 将列表按日期分组 */
 const groupByDate = (list: any[]) => {
-  const map = new Map<string, any[]>()
+  const map = new Map<string, any[]>();
   list.forEach((item: any) => {
-    const dateKey = (item.createTime || item.browseTime || '').substring(0, 10)
-    if (!map.has(dateKey)) map.set(dateKey, [])
-    map.get(dateKey)!.push(item)
-  })
-  const groups: any[] = []
+    const dateKey = (item.createTime || item.browseTime || '').substring(0, 10);
+    if (!map.has(dateKey)) map.set(dateKey, []);
+    map.get(dateKey)!.push(item);
+  });
+  const groups: any[] = [];
   map.forEach((products, dateKey) => {
     groups.push({
       date: formatDate(dateKey),
       label: getDateLabel(dateKey),
       products
-    })
-  })
-  return groups
-}
+    });
+  });
+  return groups;
+};
 
 /** 获取浏览记录列表 */
 const getFootprintList = () => {
-  loading.value = true
+  loading.value = true;
   browsingHistoryList({ pageNum: queryParams.pageNum, pageSize: queryParams.pageSize })
     .then((res: any) => {
       if (res.code == 200) {
@@ -88,64 +92,97 @@ const getFootprintList = () => {
           originalPrice: item.marketPrice || item.originalPrice || '',
           tag: item.tag || '',
           checked: false
-        }))
-        footprintGroups.value = groupByDate(list)
-        total.value = res.total || 0
+        }));
+        footprintGroups.value = groupByDate(list);
+        total.value = res.total || 0;
       }
     })
     .finally(() => {
-      loading.value = false
-    })
-}
+      loading.value = false;
+    });
+};
 
 const handleQuery = () => {
-  getFootprintList()
-}
+  getFootprintList();
+};
 
 /** 获取所有选中的商品 */
 const getSelectedItems = () => {
-  const selected: any[] = []
-  footprintGroups.value.forEach(group => {
+  const selected: any[] = [];
+  footprintGroups.value.forEach((group) => {
     group.products.forEach((item: any) => {
-      if (item.checked) selected.push(item)
-    })
-  })
-  return selected
-}
+      if (item.checked) selected.push(item);
+    });
+  });
+  return selected;
+};
 
 const handleClearAll = () => {
-  const selectedItems = getSelectedItems()
+  const selectedItems = getSelectedItems();
   if (selectedItems.length === 0) {
     ElMessageBox.confirm('确定要清空所有浏览足迹吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
-      const allIds = footprintGroups.value.flatMap(group => group.products.map((item: any) => item.id)).join(',')
-      if (!allIds) return
+      const allIds = footprintGroups.value.flatMap((group) => group.products.map((item: any) => item.id)).join(',');
+      if (!allIds) return;
       deleteProductBrowsingHistory(allIds).then((res: any) => {
         if (res.code == 200) {
-          ElMessage.success('已清空所有足迹')
-          queryParams.pageNum = 1
-          getFootprintList()
+          ElMessage.success('已清空所有足迹');
+          queryParams.pageNum = 1;
+          getFootprintList();
         }
-      })
-    })
+      });
+    });
   } else {
-    ElMessageBox.confirm(`确定要删除选中的${selectedItems.length}条足迹吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
-      const ids = selectedItems.map(item => item.id).join(',')
+    ElMessageBox.confirm(`确定要删除选中的${selectedItems.length}条足迹吗?`, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    }).then(() => {
+      const ids = selectedItems.map((item) => item.id).join(',');
       deleteProductBrowsingHistory(ids).then((res: any) => {
         if (res.code == 200) {
-          ElMessage.success('删除成功')
-          getFootprintList()
+          ElMessage.success('删除成功');
+          getFootprintList();
         }
-      })
-    })
+      });
+    });
   }
-}
+};
 
 onMounted(() => {
-  getFootprintList()
-})
+  getFootprintList();
+});
 </script>
 
 <style scoped lang="scss">
-.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; :deep(.page-title) { margin-bottom: 0; } }
-.footprint-list { .footprint-group { margin-bottom: 25px; .group-header { margin-bottom: 15px; .group-date { font-size: 14px; color: #666; &::before { content: '•'; margin-right: 8px; color: #999; } } } .product-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 15px; } } }
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  :deep(.page-title) {
+    margin-bottom: 0;
+  }
+}
+.footprint-list {
+  .footprint-group {
+    margin-bottom: 25px;
+    .group-header {
+      margin-bottom: 15px;
+      .group-date {
+        font-size: 14px;
+        color: #666;
+        &::before {
+          content: '•';
+          margin-right: 8px;
+          color: #999;
+        }
+      }
+    }
+    .product-grid {
+      display: grid;
+      grid-template-columns: repeat(5, 1fr);
+      gap: 15px;
+    }
+  }
+}
 </style>

+ 467 - 317
src/views/enterprise/purchaseHabit/index.vue

@@ -1,383 +1,533 @@
 <template>
-  <div class="purchase-habit-container">
-    <!-- 顶部返回 -->
-    <div class="page-header">
-      <el-button link @click="handleBack">
-        <el-icon><ArrowLeft /></el-icon>
-        <span>返回</span>
-      </el-button>
-      <span class="page-title">企业采购习惯</span>
+  <div class="order-manage-container">
+    <div class="page-title"><i class="title-bar"></i><span>企业采购习惯</span></div>
+    <!-- 搜索栏 -->
+    <div class="search-bar">
+      <el-input v-model="queryParams.keyword" placeholder="搜索订单号" style="width: 200px" clearable @keyup.enter="handleQuery">
+        <template #prefix
+          ><el-icon><Search /></el-icon
+        ></template>
+      </el-input>
+      <el-date-picker
+        v-model="queryParams.dateRange"
+        type="daterange"
+        range-separator="—"
+        start-placeholder="开始日期"
+        end-placeholder="结束日期"
+        style="width: 240px"
+      />
+      <el-button type="primary" @click="handleQuery">搜索</el-button>
+      <el-button @click="handleReset">重置</el-button>
     </div>
-
-    <div class="page-content">
-      <el-form ref="formRef" :model="form" label-position="top">
-        <!-- 采购金额 -->
-        <el-row :gutter="40">
-          <el-col :span="12">
-            <el-form-item label="月度采购金额">
-              <el-input v-model="form.monthlyAmount" placeholder="请输入">
-                <template #suffix>万</template>
-              </el-input>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="年度采购金额">
-              <el-input v-model="form.yearlyAmount" placeholder="请输入">
-                <template #suffix>万</template>
-              </el-input>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 产品选型 -->
-        <el-form-item label="产品选型">
-          <div class="tag-group">
-            <div
-              v-for="item in productTypeOptions"
-              :key="item"
-              :class="['tag-item', { active: form.productTypes.includes(item) }]"
-              @click="toggleTag(form.productTypes, item)"
-            >
-              {{ item }}
-            </div>
-          </div>
-        </el-form-item>
-
-        <!-- 日常打印量 -->
-        <el-form-item label="日常打印量">
-          <div class="tag-group">
-            <div
-              v-for="item in printVolumeOptions"
-              :key="item"
-              :class="['tag-item', { active: form.printVolume === item }]"
-              @click="form.printVolume = item"
-            >
-              {{ item }}
-            </div>
-          </div>
-        </el-form-item>
-
-        <!-- 购买原装耗材 & 专人进行技术服务 -->
-        <el-row :gutter="40">
-          <el-col :span="12">
-            <el-form-item label="购买原装耗材">
-              <el-radio-group v-model="form.buyOriginal">
-                <el-radio v-for="dict in sys_platform_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="专人进行技术服务">
-              <el-radio-group v-model="form.technologyService">
-                <el-radio v-for="dict in sys_platform_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 主要办公采购类目 -->
-        <el-form-item label="主要办公采购类目">
-          <div class="tag-group">
-            <div
-              v-for="item in categoryOptions"
-              :key="item"
-              :class="['tag-item', { active: form.categories.includes(item) }]"
-              @click="toggleTag(form.categories, item)"
-            >
-              {{ item }}
+    <!-- 筛选栏 -->
+    <div class="filter-bar">
+      <span class="filter-label">下单部门</span>
+      <el-tree-select
+        v-model="queryParams.department"
+        style="width: 100px"
+        :data="deptList"
+        :props="{ value: 'deptId', label: 'deptName', children: 'children' }"
+        value-key="deptId"
+        placeholder="请选择"
+        check-strictly
+        :render-after-expand="false"
+        clearable
+      >
+      </el-tree-select>
+      <span class="filter-label">状态</span>
+      <el-select v-model="queryParams.status" placeholder="请选择" style="width: 100px" clearable>
+        <el-option v-for="dict in order_status" :key="dict.value" :label="dict.label" :value="dict.value" />
+      </el-select>
+      <span class="filter-label">支付方式</span>
+      <el-select v-model="queryParams.payType" placeholder="请选择" style="width: 100px" clearable
+        ><el-option v-for="dict in pay_method" :key="dict.value" :label="dict.label" :value="dict.value"
+      /></el-select>
+    </div>
+    <!-- Tab切换 -->
+    <div class="tab-bar">
+      <div class="tab-left">
+        <div v-for="tab in statusTabs" :key="tab.key" :class="['tab-item', { active: activeTab === tab.key }]" @click="activeTab = tab.key">
+          <el-icon><component :is="tab.icon" /></el-icon><span>{{ tab.label }}</span>
+        </div>
+      </div>
+    </div>
+    <!-- 订单列表 -->
+    <div class="order-list">
+      <div v-for="order in orderList" :key="order.id" class="order-card">
+        <div class="order-header">
+          <span class="order-time">{{ order.orderTime }}</span>
+          <span class="order-info">订单号:{{ order.orderNo }}</span>
+          <span class="order-info">下单人:{{ order.orderPerson }}</span>
+          <span class="order-info">部门:{{ order.department }}</span>
+          <el-button type="primary" link class="expand-btn" @click="handleExpand(order)"
+            >{{ order.expanded ? '收起' : '展开' }} <el-icon><ArrowDown /></el-icon
+          ></el-button>
+        </div>
+        <div v-if="order.countdown" class="countdown-bar">订单锁定剩余时间:{{ order.countdown }}</div>
+        <div class="product-list">
+          <div v-for="(item, itemIndex) in order.expanded ? order.products : order.products.slice(0, 1)" :key="itemIndex" class="product-row">
+            <div class="product-cell product-info-cell">
+              <div class="product-image">
+                <el-image :src="item.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-detail">
+                <div class="product-name">{{ item.name }}</div>
+                <div class="product-spec">{{ item.spec1 }} {{ item.spec2 }}</div>
+                <div class="product-price">¥{{ item.price }}</div>
+              </div>
+              <div class="product-quantity">x{{ item.quantity }}</div>
             </div>
-          </div>
-          <el-input v-model="form.otherCategory" placeholder="其他采购类目" maxlength="50" show-word-limit class="other-input" />
-        </el-form-item>
-
-        <!-- 企业福利 -->
-        <el-form-item label="企业福利">
-          <div class="tag-group">
-            <div
-              v-for="item in welfareOptions"
-              :key="item"
-              :class="['tag-item', { active: form.welfares.includes(item) }]"
-              @click="toggleTag(form.welfares, item)"
-            >
-              {{ item }}
+            <div class="product-cell amount-cell" v-if="itemIndex === 0">
+              <div class="amount-info">
+                <span class="label">支付款</span><span class="value highlight">¥{{ order.payAmount }}</span>
+              </div>
+              <div class="amount-info">
+                <span class="label">含运费:</span><span class="value">¥{{ order.freight }}</span>
+              </div>
             </div>
-          </div>
-          <el-input v-model="form.otherScene" placeholder="其他福利" maxlength="50" show-word-limit class="other-input" />
-        </el-form-item>
-
-        <!-- 产品定制需求 -->
-        <el-form-item label="产品定制需求">
-          <div class="tag-group">
-            <div
-              v-for="item in customOptions"
-              :key="item"
-              :class="['tag-item', { active: form.customs.includes(item) }]"
-              @click="toggleTag(form.customs, item)"
-            >
-              {{ item }}
+            <div class="product-cell status-cell" v-if="itemIndex === 0">
+              <span class="status-text" :style="{ color: getStatusColor(order.status) }">{{ order.statusText }}</span>
+              <el-button type="primary" link size="small" @click="handleViewDetail(order)">查看订单轨迹</el-button>
+              <template v-if="order.auditStatus"
+                ><span :class="['audit-status', getAuditStatusClass(order.auditStatus)]">{{ order.auditStatus }}</span
+                ><el-button type="primary" link size="small">查看审批流</el-button></template
+              >
+              <el-button v-if="order.fileCount" type="primary" link size="small">审核文件({{ order.fileCount }})</el-button>
             </div>
           </div>
-          <el-input v-model="form.otherCustomize" placeholder="其他需求" maxlength="50" show-word-limit class="other-input" />
-        </el-form-item>
-      </el-form>
-
-      <!-- 底部按钮 -->
-      <div class="form-footer">
-        <el-button type="danger" @click="handleSave">保存</el-button>
-        <el-button @click="handleBack">取消</el-button>
+        </div>
       </div>
+      <el-empty v-if="orderList.length === 0" description="暂无订单" />
     </div>
+    <!-- 分页 -->
+    <TablePagination v-model:page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" :total="total" @change="handleQuery" />
   </div>
 </template>
 
 <script setup lang="ts">
-import { reactive, getCurrentInstance, toRefs, onMounted, ComponentInternalInstance, computed } from 'vue';
+import { ref, reactive, computed, onMounted, watch } from 'vue';
 import { useRouter } from 'vue-router';
-import { ArrowLeft } from '@element-plus/icons-vue';
+import { Search, ArrowDown, Document, Clock, Box, CircleCheck, CircleClose, Picture } from '@element-plus/icons-vue';
+import { TablePagination } from '@/components';
+import { getOrderList, getOrderProducts } from '@/api/pc/enterprise/order';
+import type { OrderMain } from '@/api/pc/enterprise/orderTypes';
 import { ElMessage } from 'element-plus';
-import { updatePurchaseHabit, getCustomerPurchaseHabitData } from '@/api/pc/enterprise/purchaseHabit';
-
+import { getDeptTree } from '@/api/pc/organization';
+import { DeptInfo } from '@/api/pc/organization/types';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { welfare_item, sys_platform_yes_no, product_types_choosing, purchase_item, product_customization, daily_print_volume } = toRefs<any>(
-  proxy?.useDict('welfare_item', 'sys_platform_yes_no', 'product_types_choosing', 'purchase_item', 'product_customization', 'daily_print_volume')
-);
+const { order_status, pay_method } = toRefs<any>(proxy?.useDict('order_status', 'pay_method'));
 
 const router = useRouter();
+const activeTab = ref('all');
+const loading = ref(false);
+
+const deptList = ref([]);
 
-// 根据字典数据生成选项
-const productTypeOptions = computed(() => product_types_choosing.value?.map((item: any) => item.label) || []);
-const printVolumeOptions = computed(() => daily_print_volume.value?.map((item: any) => item.label) || []);
-const categoryOptions = computed(() => purchase_item.value?.map((item: any) => item.label) || []);
-const welfareOptions = computed(() => welfare_item.value?.map((item: any) => item.label) || []);
-const customOptions = computed(() => product_customization.value?.map((item: any) => item.label) || []);
+const statusTabs = [
+  { key: 'all', label: '全部订单', icon: Document },
+  { key: 'preOrder', label: '预下单', icon: Clock },
+  { key: 'shipping', label: '待收货', icon: Box },
+  { key: 'completed', label: '已完成', icon: CircleCheck },
+  { key: 'cancelled', label: '已取消', icon: CircleClose }
+];
 
-const form = reactive({
-  id: undefined,
-  customerId: undefined,
-  customerNo: '',
-  permanentOfficer: '',
-  monthlyAmount: '',
-  yearlyAmount: '',
-  productTypes: [] as string[],
-  printVolume: '',
-  printAmount: '',
-  buyOriginal: '',
-  technologyService: '',
-  categories: [] as string[],
-  otherCategory: '',
-  welfares: [] as string[],
-  customs: [] as string[],
-  adaptScenes: [] as string[],
-  otherScene: '',
-  otherCustomize: '',
-  choiceModel: '',
-  remark: ''
+// 监听标签页切换,重置页码并重新获取数据
+watch(activeTab, (newTab) => {
+  queryParams.pageNum = 1;
+  // 根据标签页设置后端查询的状态参数
+  if (newTab === 'all') {
+    queryParams.status = '';
+  } else {
+    // 将前端标签页key映射回后端状态值
+    const tabToStatusMap: Record<string, string> = {
+      'preOrder': '0', // 待支付
+      'shipping': '4', // 待发货,部分发货,发货完成
+      'completed': '5', // 已完成
+      'cancelled': '7' // 已关闭,已取消
+    };
+    queryParams.status = tabToStatusMap[newTab] || '';
+  }
+  fetchOrderList();
 });
 
-const toggleTag = (arr: string[], item: string) => {
-  const index = arr.indexOf(item);
-  if (index > -1) arr.splice(index, 1);
-  else arr.push(item);
+const queryParams = reactive({ keyword: '', dateRange: null, department: '', status: '', payType: '', pageNum: 1, pageSize: 5 });
+const total = ref(0);
+const allOrders = ref<any[]>([]);
+
+// 将后端状态映射为前端标签页key
+const mapBackendStatusToTabKey = (backendStatus: string) => {
+  const statusMap: Record<string, string> = {
+    '0': 'preOrder', // 待支付
+    '1': 'preOrder', // 待确认
+    '2': 'shipping', // 待发货
+    '3': 'shipping', // 部分发货
+    '4': 'shipping', // 发货完成
+    '5': 'completed', // 已完成
+    '6': 'cancelled', // 已关闭
+    '7': 'cancelled' // 已取消
+  };
+  return statusMap[backendStatus] || backendStatus;
 };
-const handleBack = () => {
-  router.push('/enterprise/companyInfo');
+
+// 根据订单状态获取状态文本
+const getStatusText = (status: string) => {
+  const statusMap: Record<string, string> = {
+    '0': '待支付',
+    '1': '待确认',
+    '2': '待发货',
+    '3': '部分发货',
+    '4': '发货完成',
+    '5': '已完成',
+    '6': '已关闭',
+    '7': '已取消'
+  };
+  return statusMap[status] || status;
 };
-const handleSave = async () => {
+
+// 加载部门树
+const loadDeptTree = async () => {
   try {
-    // 将表单数据映射为接口所需格式
-    const submitData = {
-      id: form.id,
-      customerId: form.customerId,
-      customerNo: form.customerNo,
-      monthPurchase: form.monthlyAmount ? parseFloat(form.monthlyAmount) : undefined,
-      yearPurchase: form.yearlyAmount ? parseFloat(form.yearlyAmount) : undefined,
-      permanentOfficer: form.permanentOfficer,
-      choiceModel: form.productTypes.length > 0 ? product_types_choosing.value?.find((i: any) => i.label === form.productTypes[0])?.value || '' : '',
-      printAmount: form.printVolume ? daily_print_volume.value?.find((i: any) => i.label === form.printVolume)?.value || '' : '',
-      buyOriginal: form.buyOriginal,
-      technologyService: form.technologyService,
-      purchaseCategory:
-        form.categories.length > 0
-          ? form.categories
-              .map((label) => {
-                const item = purchase_item.value?.find((i: any) => i.label === label);
-                return item ? item.value : '';
-              })
-              .filter(Boolean)
-              .join(',')
-          : undefined,
-      otherCategory: form.otherCategory || undefined,
-      adaptScene:
-        form.welfares.length > 0
-          ? form.welfares
-              .map((label) => {
-                const item = welfare_item.value?.find((i: any) => i.label === label);
-                return item ? item.value : '';
-              })
-              .filter(Boolean)
-              .join(',')
-          : undefined,
-      otherScene: form.otherScene || undefined,
-      customizeDemand:
-        form.customs.length > 0
-          ? form.customs
-              .map((label) => {
-                const item = product_customization.value?.find((i: any) => i.label === label);
-                return item ? item.value : '';
-              })
-              .filter(Boolean)
-              .join(',')
-          : undefined,
-      otherCustomize: form.otherCustomize || undefined,
-      remark: form.remark || undefined
-    };
+    const res = await getDeptTree();
+    if (res.code === 200 && res.data) {
+      deptList.value = res.data;
 
-    await updatePurchaseHabit(submitData);
-    ElMessage.success('保存成功');
-    handleBack();
+      if (Array.isArray(res.data)) {
+        const treeData = proxy?.handleTree<DeptInfo>(res.data, 'deptId', 'parentId');
+        deptList.value = treeData || res.data;
+      } else {
+        deptList.value = [];
+      }
+    }
   } catch (error) {
-    ElMessage.error('保存失败');
+    console.error('获取部门树失败:', error);
+    ElMessage.error('获取部门树失败');
   }
 };
 
-const getPurchaseHabitData = async () => {
+// 获取订单列表
+const fetchOrderList = async () => {
+  loading.value = true;
   try {
-    const res = await getCustomerPurchaseHabitData();
-    if (res.code === 200 && res.data) {
-      const data = res.data;
-
-      // 映射数据到表单
-      form.id = data.id;
-      form.customerId = data.customerId;
-      form.customerNo = data.customerNo || '';
-      form.permanentOfficer = data.permanentOfficer || '';
-      form.monthlyAmount = data.monthPurchase || '';
-      form.yearlyAmount = data.yearPurchase || '';
-
-      // 处理产品选型(单选)
-      form.productTypes = data.choiceModel ? [product_types_choosing.value?.find((i: any) => i.value === data.choiceModel)?.label || ''] : [];
+    const params: any = {
+      pageNum: queryParams.pageNum,
+      pageSize: queryParams.pageSize
+    };
 
-      // 处理日常打印量(单选)
-      form.printVolume = data.printAmount ? daily_print_volume.value?.find((i: any) => i.value === data.printAmount)?.label || '' : '';
+    // 添加筛选条件
+    if (queryParams.keyword) params.orderNo = queryParams.keyword;
+    if (queryParams.department) params.department = queryParams.department;
+    if (queryParams.status) params.orderStatuses = queryParams.status; // 使用orderStatuses支持多状态查询
+    if (queryParams.payType) params.payType = queryParams.payType;
+    if (queryParams.dateRange && queryParams.dateRange.length === 2) {
+      params.beginTime = queryParams.dateRange[0];
+      params.endTime = queryParams.dateRange[1];
+    }
 
-      form.categories = data.purchaseCategory
-        ? data.purchaseCategory
-            .split(',')
-            .map((id: string) => {
-              const item = purchase_item.value?.find((i: any) => i.value === id);
-              return item ? item.label : '';
-            })
-            .filter(Boolean)
-        : [];
+    const res = await getOrderList(params);
+    if (res.code === 200) {
+      // 将后端数据转换为前端需要的格式
+      allOrders.value = (res.rows || []).map((order: OrderMain) => ({
+        id: order.id,
+        orderTime: order.createTime,
+        orderNo: order.orderNo,
+        orderPerson: order.customerName, // 需要关联用户信息
+        department: order.createDeptName, // 需要关联部门信息
+        payAmount: order.payableAmount || 0,
+        freight: order.shippingFee || 0,
+        status: mapBackendStatusToTabKey(order.orderStatus || ''),
+        statusText: getStatusText(order.orderStatus || ''),
+        countdown: '',
+        auditStatus: order.checkStatus,
+        fileCount: 0,
+        expanded: false,
+        products: [] // 商品信息需要单独加载
+      }));
 
-      form.welfares = data.adaptScene
-        ? data.adaptScene
-            .split(',')
-            .map((id: string) => {
-              const item = welfare_item.value?.find((i: any) => i.value === id);
-              return item ? item.label : '';
-            })
-            .filter(Boolean)
-        : [];
+      total.value = res.total || 0;
+    }
+  } catch (error) {
+    console.error('获取订单列表失败:', error);
+  } finally {
+    loading.value = false;
+  }
+};
 
-      form.customs = data.customizeDemand
-        ? data.customizeDemand
-            .split(',')
-            .map((id: string) => {
-              const item = product_customization.value?.find((i: any) => i.value === id);
-              return item ? item.label : '';
-            })
-            .filter(Boolean)
-        : [];
+const orderList = computed(() => allOrders.value);
 
-      // 处理单选字段
-      form.printAmount = data.printAmount || '';
+const getStatusColor = (status: string) =>
+  ({ completed: '#67c23a', preOrder: '#e6a23c', shipping: '#409eff', cancelled: '#909399' })[status] || '#909399';
+const getAuditStatusClass = (auditStatus: string) => (auditStatus === '审批通过' ? 'success' : auditStatus === '审批驳回' ? 'danger' : 'warning');
 
-      form.buyOriginal = data.buyOriginal || '';
+const handleExpand = async (order: any) => {
+  const orderIndex = allOrders.value.findIndex((o) => o.id === order.id);
+  if (orderIndex === -1) return;
 
-      form.technologyService = data.technologyService;
+  const currentOrder = allOrders.value[orderIndex];
 
-      form.choiceModel = data.choiceModel || '';
+  if (!currentOrder.expanded && currentOrder.products.length === 0) {
+    try {
+      const res = await getOrderProducts([order.id]);
+      if (res.code === 200 && res.rows) {
+        const products = res.rows.map((p: any) => ({
+          image: p.productImage || '',
+          name: p.productName || '',
+          spec1: p.productUnit || '',
+          spec2: p.productNo || '',
+          price: p.orderPrice || 0,
+          quantity: p.orderQuantity || 0
+        }));
 
-      // 其他字段
-      form.otherCategory = data.otherCategory || '';
-      form.otherCustomize = data.otherCustomize || '';
-      form.otherScene = data.otherScene || '';
-      form.otherCustomize = data.otherCustomize || '';
-      form.remark = data.remark || '';
+        // 替换整个数组以触发响应式更新
+        allOrders.value = allOrders.value.map((o, i) => (i === orderIndex ? { ...o, expanded: true, products } : o));
+        return;
+      }
+    } catch (error) {
+      console.error('加载商品失败:', error);
+      return;
     }
-  } catch (error) {
-    console.error('获取采购习惯数据失败:', error);
-    ElMessage.error('获取数据失败');
   }
+
+  // 替换整个数组以触发响应式更新
+  allOrders.value = allOrders.value.map((o, i) => (i === orderIndex ? { ...o, expanded: !o.expanded } : o));
+};
+
+const handleViewDetail = (order: any) => {
+  router.push(`/order/orderManage/detail/${order.orderNo}`);
+};
+const handleQuery = () => {
+  queryParams.pageNum = 1;
+  fetchOrderList();
+};
+const handleReset = () => {
+  queryParams.keyword = '';
+  queryParams.dateRange = null;
+  queryParams.department = '';
+  queryParams.payType = '';
+  queryParams.pageNum = 1;
+  fetchOrderList();
 };
 
+// 页面加载时获取订单列表
 onMounted(() => {
-  getPurchaseHabitData();
+  loadDeptTree();
+  fetchOrderList();
 });
 </script>
 
 <style scoped lang="scss">
-.purchase-habit-container {
-  background: #f5f5f5;
+.order-manage-container {
+  padding: 20px;
+  background: #fff;
   min-height: 100%;
+  display: flex;
+  flex-direction: column;
+  max-height: calc(100vh - 120px);
 }
-.page-header {
-  background: #fff;
-  padding: 15px 20px;
+.page-title {
+  font-size: 16px;
+  font-weight: bold;
   display: flex;
   align-items: center;
-  gap: 10px;
-  border-bottom: 1px solid #eee;
-  .page-title {
-    font-size: 16px;
-    font-weight: bold;
-    color: #333;
-  }
+  gap: 8px;
+  margin-bottom: 20px;
 }
-.page-content {
-  padding: 20px;
-  background: #fff;
-  margin: 20px;
-  border-radius: 8px;
+.title-bar {
+  display: inline-block;
+  width: 3px;
+  height: 16px;
+  background: #e60012;
+  border-radius: 2px;
 }
-.tag-group {
+.search-bar {
   display: flex;
-  flex-wrap: wrap;
+  align-items: center;
+  gap: 15px;
+  margin-bottom: 15px;
+}
+.filter-bar {
+  display: flex;
+  align-items: center;
   gap: 10px;
-  .tag-item {
-    padding: 8px 20px;
-    border: 1px solid #ddd;
-    border-radius: 4px;
-    cursor: pointer;
+  margin-bottom: 15px;
+  .filter-label {
     font-size: 14px;
     color: #666;
-    transition: all 0.2s;
-    &:hover {
-      border-color: #e60012;
-      color: #e60012;
+  }
+}
+.tab-bar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border-bottom: 1px solid #eee;
+  margin-bottom: 15px;
+  .tab-left {
+    display: flex;
+    gap: 25px;
+  }
+  .tab-item {
+    display: flex;
+    align-items: center;
+    gap: 5px;
+    padding: 12px 0;
+    cursor: pointer;
+    color: #666;
+    font-size: 14px;
+    border-bottom: 2px solid transparent;
+    margin-bottom: -1px;
+    &:hover,
+    &.active {
+      color: #333;
     }
     &.active {
-      border-color: #e60012;
-      color: #e60012;
-      background: #fff5f5;
+      border-bottom-color: #e60012;
     }
   }
 }
-.other-input {
-  margin-top: 10px;
-}
-.form-footer {
-  text-align: center;
-  padding-top: 30px;
-  border-top: 1px solid #eee;
-  margin-top: 20px;
-}
-:deep(.el-form-item__label) {
-  font-weight: 500;
-  color: #333;
+.order-list {
+  flex: 1;
+  overflow-y: auto;
+  margin-bottom: 15px;
+  .order-card {
+    border: 1px solid #eee;
+    border-radius: 4px;
+    margin-bottom: 15px;
+    overflow: hidden;
+    .order-header {
+      display: flex;
+      align-items: center;
+      gap: 15px;
+      padding: 12px 15px;
+      background: #f9f9f9;
+      border-bottom: 1px solid #eee;
+      font-size: 13px;
+      color: #666;
+      .order-time {
+        color: #333;
+      }
+      .expand-btn {
+        margin-left: auto;
+      }
+    }
+    .countdown-bar {
+      background: #fff5e6;
+      color: #e6a23c;
+      padding: 8px 15px;
+      font-size: 13px;
+      border-bottom: 1px solid #eee;
+    }
+    .product-list {
+      .product-row {
+        display: flex;
+        border-bottom: 1px solid #f5f5f5;
+        &:last-child {
+          border-bottom: none;
+        }
+      }
+      .product-cell {
+        padding: 15px;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+      }
+      .product-info-cell {
+        flex: 1;
+        flex-direction: row;
+        align-items: center;
+        gap: 15px;
+        .product-image {
+          width: 80px;
+          height: 80px;
+          background: #f5f5f5;
+          border-radius: 4px;
+          overflow: hidden;
+          flex-shrink: 0;
+          .el-image {
+            width: 100%;
+            height: 100%;
+          }
+          .image-placeholder {
+            width: 100%;
+            height: 100%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+          }
+        }
+        .product-detail {
+          flex: 1;
+          .product-name {
+            font-size: 14px;
+            color: #333;
+            margin-bottom: 5px;
+            line-height: 1.4;
+          }
+          .product-spec {
+            font-size: 12px;
+            color: #999;
+            margin-bottom: 5px;
+          }
+          .product-price {
+            font-size: 16px;
+            font-weight: bold;
+            color: #e60012;
+          }
+        }
+        .product-quantity {
+          font-size: 13px;
+          color: #666;
+        }
+      }
+      .amount-cell {
+        width: 120px;
+        border-left: 1px solid #f5f5f5;
+        .amount-info {
+          margin-bottom: 5px;
+          .label {
+            font-size: 12px;
+            color: #999;
+          }
+          .value {
+            font-size: 14px;
+            color: #333;
+            &.highlight {
+              font-size: 16px;
+              font-weight: bold;
+              color: #e60012;
+            }
+          }
+        }
+      }
+      .status-cell {
+        width: 120px;
+        border-left: 1px solid #f5f5f5;
+        align-items: flex-start;
+        gap: 5px;
+        .status-text {
+          font-size: 14px;
+          font-weight: 500;
+        }
+        .audit-status {
+          font-size: 12px;
+          &.success {
+            color: #e60012;
+          }
+          &.warning {
+            color: #e6a23c;
+          }
+          &.danger {
+            color: #e60012;
+          }
+        }
+      }
+    }
+  }
 }
-:deep(.el-radio) {
-  margin-right: 30px;
+:deep(.table-pagination) {
+  flex-shrink: 0;
+  margin-top: 15px;
 }
 </style>

+ 547 - 113
src/views/enterprise/purchaseHistory/index.vue

@@ -1,149 +1,583 @@
 <template>
-  <div class="page-container">
-    <PageTitle title="历史购买" />
+  <div class="order-manage-container">
+    <div class="page-title"><i class="title-bar"></i><span>历史购买</span></div>
     <!-- 搜索栏 -->
     <div class="search-bar">
-      <el-input v-model="queryParams.keyword" placeholder="搜索" style="width: 180px" clearable>
-        <template #prefix><el-icon><Search /></el-icon></template>
+      <el-input v-model="queryParams.keyword" placeholder="搜索订单号" style="width: 200px" clearable @keyup.enter="handleQuery">
+        <template #prefix
+          ><el-icon><Search /></el-icon
+        ></template>
       </el-input>
-      <div class="price-range">
-        <el-input v-model="queryParams.minPrice" placeholder="¥ 最高价" style="width: 100px" />
-        <span class="range-separator">—</span>
-        <el-input v-model="queryParams.maxPrice" placeholder="¥ 最低价" style="width: 100px" />
-      </div>
-      <el-date-picker v-model="queryParams.dateRange" type="daterange" range-separator="—" start-placeholder="请选择购买时间" end-placeholder="" style="width: 240px" />
+      <el-date-picker
+        v-model="queryParams.dateRange"
+        type="daterange"
+        range-separator="—"
+        start-placeholder="开始日期"
+        end-placeholder="结束日期"
+        style="width: 240px"
+      />
+      <el-button type="primary" @click="handleQuery">搜索</el-button>
+      <el-button @click="handleReset">重置</el-button>
     </div>
     <!-- 筛选栏 -->
     <div class="filter-bar">
-      <span class="filter-label">商品类别</span>
-      <el-select v-model="queryParams.category1" placeholder="请选择" style="width: 100px" clearable @change="handleCategory1Change">
-        <el-option label="电脑" value="电脑" />
-        <el-option label="办公设备" value="办公设备" />
-        <el-option label="家用电器" value="家用电器" />
-      </el-select>
-      <el-select v-model="queryParams.category2" placeholder="请选择" style="width: 100px" clearable @change="handleCategory2Change">
-        <el-option v-for="item in category2Options" :key="item" :label="item" :value="item" />
-      </el-select>
-      <el-select v-model="queryParams.category3" placeholder="请选择" style="width: 100px" clearable>
-        <el-option v-for="item in category3Options" :key="item" :label="item" :value="item" />
-      </el-select>
-      <span class="filter-label">商品品牌</span>
-      <el-select v-model="queryParams.brand" placeholder="请选择" style="width: 100px" clearable>
-        <el-option label="清华同方" value="清华同方" />
-        <el-option label="联想" value="联想" />
-        <el-option label="戴尔" value="戴尔" />
+      <span class="filter-label">下单部门</span>
+      <el-tree-select
+        v-model="queryParams.department"
+        style="width: 100px"
+        :data="deptList"
+        :props="{ value: 'deptId', label: 'deptName', children: 'children' }"
+        value-key="deptId"
+        placeholder="请选择"
+        check-strictly
+        :render-after-expand="false"
+        clearable
+      >
+      </el-tree-select>
+      <span class="filter-label">状态</span>
+      <el-select v-model="queryParams.status" placeholder="请选择" style="width: 100px" clearable>
+        <el-option v-for="dict in order_status" :key="dict.value" :label="dict.label" :value="dict.value" />
       </el-select>
+      <span class="filter-label">支付方式</span>
+      <el-select v-model="queryParams.payType" placeholder="请选择" style="width: 100px" clearable
+        ><el-option v-for="dict in pay_method" :key="dict.value" :label="dict.label" :value="dict.value"
+      /></el-select>
     </div>
-    <!-- 排序栏 -->
-    <div class="sort-bar">
-      <el-select v-model="queryParams.sortType" placeholder="默认排序" style="width: 110px"><el-option label="默认排序" value="default" /><el-option label="购买时间" value="time" /></el-select>
-      <el-select v-model="queryParams.priceSort" placeholder="价格排序" style="width: 110px"><el-option label="价格排序" value="" /><el-option label="价格从低到高" value="asc" /><el-option label="价格从高到低" value="desc" /></el-select>
+    <!-- Tab切换 -->
+    <div class="tab-bar">
+      <div class="tab-left">
+        <div v-for="tab in statusTabs" :key="tab.key" :class="['tab-item', { active: activeTab === tab.key }]" @click="activeTab = tab.key">
+          <el-icon><component :is="tab.icon" /></el-icon><span>{{ tab.label }}</span>
+        </div>
+      </div>
     </div>
     <!-- 订单列表 -->
     <div class="order-list">
-      <div v-for="(order, orderIndex) in orderList" :key="orderIndex" class="order-card">
+      <div v-for="order in orderList" :key="order.id" class="order-card">
         <div class="order-header">
-          <div class="order-info">
-            <span class="order-date">{{ order.date }}</span>
-            <span class="order-no">订单号:{{ order.orderNo }}</span>
-          </div>
-          <el-button type="danger" link @click="handleReorder(order)">一键复购</el-button>
+          <span class="order-time">{{ order.orderTime }}</span>
+          <span class="order-info">订单号:{{ order.orderNo }}</span>
+          <span class="order-info">下单人:{{ order.orderPerson }}</span>
+          <span class="order-info">部门:{{ order.department }}</span>
+          <el-button type="danger" size="small" @click="handleAddCart(order)">批量加入购物车</el-button>
+          <el-button type="primary" link class="expand-btn" @click="handleExpand(order)"
+            >{{ order.expanded ? '收起' : '展开' }} <el-icon><ArrowDown /></el-icon
+          ></el-button>
         </div>
+        <div v-if="order.countdown" class="countdown-bar">订单锁定剩余时间:{{ order.countdown }}</div>
         <div class="product-list">
-          <div v-for="(item, itemIndex) in order.products" :key="itemIndex" class="product-item">
-            <div class="product-image">
-              <el-image :src="item.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">{{ item.name }}</div>
-              <div class="product-spec">{{ item.spec1 }}</div>
-              <div class="product-spec">{{ item.spec2 }}</div>
+          <div v-for="(item, itemIndex) in order.expanded ? order.products : order.products.slice(0, 1)" :key="itemIndex" class="product-row">
+            <div class="product-cell product-info-cell">
+              <div class="product-image">
+                <el-image :src="item.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-detail">
+                <div class="product-name">{{ item.name }}</div>
+                <div class="product-spec">{{ item.spec1 }} {{ item.spec2 }}</div>
+                <div class="product-price">¥{{ item.price }}</div>
+              </div>
+              <div class="product-quantity">x{{ item.quantity }}</div>
             </div>
-            <div class="product-price">
-              <span class="price">¥{{ item.price }}</span>
-              <span class="quantity">x{{ item.quantity }}</span>
+            <div class="product-cell amount-cell" v-if="itemIndex === 0">
+              <div class="amount-info">
+                <span class="label">支付款</span><span class="value highlight">¥{{ order.payAmount }}</span>
+              </div>
+              <div class="amount-info">
+                <span class="label">含运费:</span><span class="value">¥{{ order.freight }}</span>
+              </div>
             </div>
-            <div class="product-total" v-if="itemIndex === 0">
-              <span class="total-label">支付款</span>
-              <span class="total-price">¥{{ order.totalAmount }}</span>
+            <!-- <div class="product-cell status-cell" v-if="itemIndex === 0">
+              <span class="status-text" :style="{ color: getStatusColor(order.status) }">{{ order.statusText }}</span>
+              <el-button type="primary" link size="small" @click="handleViewDetail(order)">查看订单轨迹</el-button>
+              <template v-if="order.auditStatus"
+                ><span :class="['audit-status', getAuditStatusClass(order.auditStatus)]">{{ order.auditStatus }}</span
+                ><el-button type="primary" link size="small">查看审批流</el-button></template
+              >
+              <el-button v-if="order.fileCount" type="primary" link size="small">审核文件({{ order.fileCount }})</el-button>
+            </div> -->
+            <div class="product-cell action-cell" v-if="itemIndex === 0">
+              <el-button type="primary" link size="small" @click="handleAddCart(order)">加入购物车</el-button>
             </div>
-            <div class="product-action"><el-button type="danger" link @click="handleBuyAgain(item)">再次购买</el-button></div>
           </div>
         </div>
       </div>
+      <el-empty v-if="orderList.length === 0" description="暂无订单" />
     </div>
-    <el-empty v-if="orderList.length === 0" description="暂无购买记录" />
-    <TablePagination v-if="orderList.length > 0" v-model:page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" :total="total" @change="handleQuery" />
+    <!-- 分页 -->
+    <TablePagination v-model:page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" :total="total" @change="handleQuery" />
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, computed } from 'vue'
-import { Search, Picture } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
-import { PageTitle, TablePagination } from '@/components'
-
-const queryParams = reactive({ pageNum: 1, pageSize: 10, keyword: '', minPrice: '', maxPrice: '', dateRange: null, category1: '', category2: '', category3: '', brand: '', sortType: 'default', priceSort: '' })
-const total = ref(100)
-
-// 三级分类数据
-const categoryData: Record<string, Record<string, string[]>> = {
-  '电脑': {
-    '台式机': ['商用台式机', '家用台式机', '一体机'],
-    '笔记本': ['商务本', '游戏本', '轻薄本'],
-    '平板电脑': ['安卓平板', 'iPad', 'Windows平板']
-  },
-  '办公设备': {
-    '打印机': ['激光打印机', '喷墨打印机', '针式打印机'],
-    '复印机': ['黑白复印机', '彩色复印机'],
-    '投影仪': ['商务投影', '家用投影']
-  },
-  '家用电器': {
-    '空调': ['挂机', '柜机', '中央空调'],
-    '冰箱': ['双门冰箱', '多门冰箱', '对开门冰箱'],
-    '洗衣机': ['滚筒洗衣机', '波轮洗衣机']
+import { ref, reactive, computed, onMounted, watch } from 'vue';
+import { useRouter } from 'vue-router';
+import { Search, ArrowDown, Document, Clock, Box, CircleCheck, CircleClose, Picture } from '@element-plus/icons-vue';
+import { TablePagination } from '@/components';
+import { getOrderList, getOrderProducts } from '@/api/pc/enterprise/order';
+import type { OrderMain } from '@/api/pc/enterprise/orderTypes';
+import { ElMessage } from 'element-plus';
+import { getDeptTree } from '@/api/pc/organization';
+import { DeptInfo } from '@/api/pc/organization/types';
+import { addProductShoppingCart } from '@/api/goods/index';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { order_status, pay_method } = toRefs<any>(proxy?.useDict('order_status', 'pay_method'));
+
+const router = useRouter();
+const activeTab = ref('all');
+const loading = ref(false);
+
+const deptList = ref([]);
+
+const statusTabs = [
+  { key: 'all', label: '全部订单', icon: Document },
+  { key: 'preOrder', label: '预下单', icon: Clock },
+  { key: 'shipping', label: '待收货', icon: Box },
+  { key: 'completed', label: '已完成', icon: CircleCheck },
+  { key: 'cancelled', label: '已取消', icon: CircleClose }
+];
+
+// 监听标签页切换,重置页码并重新获取数据
+watch(activeTab, (newTab) => {
+  queryParams.pageNum = 1;
+  // 根据标签页设置后端查询的状态参数
+  if (newTab === 'all') {
+    queryParams.status = '';
+  } else {
+    // 将前端标签页key映射回后端状态值
+    const tabToStatusMap: Record<string, string> = {
+      'preOrder': '0', // 待支付
+      'shipping': '4', // 待发货,部分发货,发货完成
+      'completed': '5', // 已完成
+      'cancelled': '7' // 已关闭,已取消
+    };
+    queryParams.status = tabToStatusMap[newTab] || '';
   }
-}
+  fetchOrderList();
+});
 
-const category2Options = computed(() => {
-  if (!queryParams.category1) return []
-  return Object.keys(categoryData[queryParams.category1] || {})
-})
+const queryParams = reactive({ keyword: '', dateRange: null, department: '', status: '', payType: '', pageNum: 1, pageSize: 5 });
+const total = ref(0);
+const allOrders = ref<any[]>([]);
 
-const category3Options = computed(() => {
-  if (!queryParams.category1 || !queryParams.category2) return []
-  return categoryData[queryParams.category1]?.[queryParams.category2] || []
-})
+// 将后端状态映射为前端标签页key
+const mapBackendStatusToTabKey = (backendStatus: string) => {
+  const statusMap: Record<string, string> = {
+    '0': 'preOrder', // 待支付
+    '1': 'preOrder', // 待确认
+    '2': 'shipping', // 待发货
+    '3': 'shipping', // 部分发货
+    '4': 'shipping', // 发货完成
+    '5': 'completed', // 已完成
+    '6': 'cancelled', // 已关闭
+    '7': 'cancelled' // 已取消
+  };
+  return statusMap[backendStatus] || backendStatus;
+};
 
-const handleCategory1Change = () => {
-  queryParams.category2 = ''
-  queryParams.category3 = ''
-}
+// 根据订单状态获取状态文本
+const getStatusText = (status: string) => {
+  const statusMap: Record<string, string> = {
+    '0': '待支付',
+    '1': '待确认',
+    '2': '待发货',
+    '3': '部分发货',
+    '4': '发货完成',
+    '5': '已完成',
+    '6': '已关闭',
+    '7': '已取消'
+  };
+  return statusMap[status] || status;
+};
 
-const handleCategory2Change = () => {
-  queryParams.category3 = ''
-}
+// 加载部门树
+const loadDeptTree = async () => {
+  try {
+    const res = await getDeptTree();
+    if (res.code === 200 && res.data) {
+      deptList.value = res.data;
+
+      if (Array.isArray(res.data)) {
+        const treeData = proxy?.handleTree<DeptInfo>(res.data, 'deptId', 'parentId');
+        deptList.value = treeData || res.data;
+      } else {
+        deptList.value = [];
+      }
+    }
+  } catch (error) {
+    console.error('获取部门树失败:', error);
+    ElMessage.error('获取部门树失败');
+  }
+};
+
+// 获取订单列表
+const fetchOrderList = async () => {
+  loading.value = true;
+  try {
+    const params: any = {
+      pageNum: queryParams.pageNum,
+      pageSize: queryParams.pageSize
+    };
+
+    // 添加筛选条件
+    if (queryParams.keyword) params.orderNo = queryParams.keyword;
+    if (queryParams.department) params.department = queryParams.department;
+    if (queryParams.status) params.orderStatuses = queryParams.status; // 使用orderStatuses支持多状态查询
+    if (queryParams.payType) params.payType = queryParams.payType;
+    if (queryParams.dateRange && queryParams.dateRange.length === 2) {
+      params.beginTime = queryParams.dateRange[0];
+      params.endTime = queryParams.dateRange[1];
+    }
+
+    const res = await getOrderList(params);
+    if (res.code === 200) {
+      // 将后端数据转换为前端需要的格式
+      allOrders.value = (res.rows || []).map((order: OrderMain) => ({
+        id: order.id,
+        orderTime: order.createTime,
+        orderNo: order.orderNo,
+        orderPerson: order.customerName, // 需要关联用户信息
+        department: order.createDeptName, // 需要关联部门信息
+        payAmount: order.payableAmount || 0,
+        freight: order.shippingFee || 0,
+        status: mapBackendStatusToTabKey(order.orderStatus || ''),
+        statusText: getStatusText(order.orderStatus || ''),
+        countdown: '',
+        auditStatus: order.checkStatus,
+        fileCount: 0,
+        expanded: false,
+        products: [] // 商品信息需要单独加载
+      }));
+
+      total.value = res.total || 0;
+    }
+  } catch (error) {
+    console.error('获取订单列表失败:', error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+const orderList = computed(() => allOrders.value);
+
+const getStatusColor = (status: string) =>
+  ({ completed: '#67c23a', preOrder: '#e6a23c', shipping: '#409eff', cancelled: '#909399' })[status] || '#909399';
+const getAuditStatusClass = (auditStatus: string) => (auditStatus === '审批通过' ? 'success' : auditStatus === '审批驳回' ? 'danger' : 'warning');
 
-const orderList = ref([
-  { date: '2025/12/05', orderNo: '489283929283298392', totalAmount: '181', products: [
-    { id: 1, name: '清华同方超越E500台式机电脑(i3-6100/4G/1T/19.5寸)', spec1: '规格02', spec2: '规格01', price: '181', quantity: 1, image: '' },
-    { id: 2, name: '清华同方超越E500台式机电脑(i3-6100/4G/1T/19.5寸)', spec1: '规格02', spec2: '规格01', price: '181', quantity: 1, image: '' }
-  ]},
-  { date: '2025/12/05', orderNo: '489283929283298393', totalAmount: '181', products: [
-    { id: 3, name: '清华同方超越E500台式机电脑(i3-6100/4G/1T/19.5寸)', spec1: '规格02', spec2: '规格01', price: '181', quantity: 1, image: '' }
-  ]}
-])
-
-const handleQuery = () => {}
-const handleReorder = (_order: any) => { ElMessage.success('已将订单商品加入购物车') }
-const handleBuyAgain = (_item: any) => { ElMessage.success('已加入购物车') }
+const handleExpand = async (order: any) => {
+  const orderIndex = allOrders.value.findIndex((o) => o.id === order.id);
+  if (orderIndex === -1) return;
+
+  const currentOrder = allOrders.value[orderIndex];
+
+  if (!currentOrder.expanded && currentOrder.products.length === 0) {
+    try {
+      const res = await getOrderProducts([order.id]);
+      if (res.code === 200 && res.rows) {
+        const products = res.rows.map((p: any) => ({
+          id: p.productId || p.id,
+          image: p.productImage || '',
+          name: p.productName || '',
+          spec1: p.productUnit || '',
+          spec2: p.productNo || '',
+          price: p.orderPrice || 0,
+          quantity: p.orderQuantity || 0
+        }));
+
+        // 替换整个数组以触发响应式更新
+        allOrders.value = allOrders.value.map((o, i) => (i === orderIndex ? { ...o, expanded: true, products } : o));
+        return;
+      }
+    } catch (error) {
+      console.error('加载商品失败:', error);
+      return;
+    }
+  }
+
+  // 替换整个数组以触发响应式更新
+  allOrders.value = allOrders.value.map((o, i) => (i === orderIndex ? { ...o, expanded: !o.expanded } : o));
+};
+
+const handleViewDetail = (order: any) => {
+  router.push(`/order/orderManage/detail/${order.orderNo}`);
+};
+const handleQuery = () => {
+  queryParams.pageNum = 1;
+  fetchOrderList();
+};
+const handleReset = () => {
+  queryParams.keyword = '';
+  queryParams.dateRange = null;
+  queryParams.department = '';
+  queryParams.payType = '';
+  queryParams.pageNum = 1;
+  fetchOrderList();
+};
+
+// 加入购物车
+const handleAddCart = async (order: any) => {
+  try {
+    // 如果商品列表为空,先加载商品
+    if (!order.products || order.products.length === 0) {
+      const res = await getOrderProducts([order.id]);
+      if (res.code === 200 && res.rows) {
+        order.products = res.rows.map((p: any) => ({
+          id: p.productId || p.id,
+          image: p.productImage || '',
+          name: p.productName || '',
+          spec1: p.productUnit || '',
+          spec2: p.productNo || '',
+          price: p.orderPrice || 0,
+          quantity: p.orderQuantity || 0
+        }));
+      } else {
+        ElMessage.warning('无法获取订单商品信息');
+        return;
+      }
+    }
+
+    // 批量加入购物车
+    const promises = order.products.map((product: any) =>
+      addProductShoppingCart({
+        productId: product.id,
+        productNum: product.quantity || 1
+      })
+    );
+
+    await Promise.all(promises);
+    ElMessage.success(`已将${order.products.length}件商品加入购物车`);
+  } catch (error) {
+    console.error('加入购物车失败:', error);
+    ElMessage.error('加入购物车失败');
+  }
+};
+
+// 页面加载时获取订单列表
+onMounted(() => {
+  loadDeptTree();
+  fetchOrderList();
+});
 </script>
 
 <style scoped lang="scss">
-.search-bar { display: flex; align-items: center; gap: 15px; margin-bottom: 15px; .price-range { display: flex; align-items: center; gap: 5px; .range-separator { color: #999; } } }
-.filter-bar { display: flex; align-items: center; gap: 10px; margin-bottom: 15px; .filter-label { font-size: 14px; color: #666; } }
-.sort-bar { display: flex; gap: 10px; margin-bottom: 20px; }
-.order-list { .order-card { border: 1px solid #eee; border-radius: 4px; margin-bottom: 15px; overflow: hidden; .order-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 15px; background: #f9f9f9; border-bottom: 1px solid #eee; .order-info { display: flex; align-items: center; gap: 20px; .order-date { font-size: 14px; color: #333; font-weight: 500; } .order-no { font-size: 13px; color: #666; } } } .product-list { .product-item { display: flex; align-items: center; padding: 15px; border-bottom: 1px solid #f5f5f5; &:last-child { border-bottom: none; } .product-image { width: 80px; height: 80px; background: #f5f5f5; border-radius: 4px; overflow: hidden; flex-shrink: 0; .el-image { width: 100%; height: 100%; } .image-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; } } .product-info { flex: 1; padding: 0 20px; .product-name { font-size: 14px; color: #333; margin-bottom: 8px; line-height: 1.4; } .product-spec { font-size: 12px; color: #999; margin-bottom: 3px; } } .product-price { width: 100px; text-align: center; .price { display: block; font-size: 16px; font-weight: bold; color: #e60012; } .quantity { display: block; font-size: 12px; color: #999; margin-top: 5px; } } .product-total { width: 120px; text-align: center; .total-label { display: block; font-size: 12px; color: #999; } .total-price { display: block; font-size: 16px; font-weight: bold; color: #e60012; margin-top: 5px; } } .product-action { width: 80px; text-align: right; } } } } }
+.order-manage-container {
+  padding: 20px;
+  background: #fff;
+  min-height: 100%;
+  display: flex;
+  flex-direction: column;
+  max-height: calc(100vh - 120px);
+}
+.page-title {
+  font-size: 16px;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 20px;
+}
+.title-bar {
+  display: inline-block;
+  width: 3px;
+  height: 16px;
+  background: #e60012;
+  border-radius: 2px;
+}
+.search-bar {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  margin-bottom: 15px;
+}
+.filter-bar {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 15px;
+  .filter-label {
+    font-size: 14px;
+    color: #666;
+  }
+}
+.tab-bar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border-bottom: 1px solid #eee;
+  margin-bottom: 15px;
+  .tab-left {
+    display: flex;
+    gap: 25px;
+  }
+  .tab-item {
+    display: flex;
+    align-items: center;
+    gap: 5px;
+    padding: 12px 0;
+    cursor: pointer;
+    color: #666;
+    font-size: 14px;
+    border-bottom: 2px solid transparent;
+    margin-bottom: -1px;
+    &:hover,
+    &.active {
+      color: #333;
+    }
+    &.active {
+      border-bottom-color: #e60012;
+    }
+  }
+}
+.order-list {
+  flex: 1;
+  overflow-y: auto;
+  margin-bottom: 15px;
+  .order-card {
+    border: 1px solid #eee;
+    border-radius: 4px;
+    margin-bottom: 15px;
+    overflow: hidden;
+    .order-header {
+      display: flex;
+      align-items: center;
+      gap: 15px;
+      padding: 12px 15px;
+      background: #f9f9f9;
+      border-bottom: 1px solid #eee;
+      font-size: 13px;
+      color: #666;
+      .order-time {
+        color: #333;
+      }
+      .expand-btn {
+        margin-left: auto;
+      }
+    }
+    .countdown-bar {
+      background: #fff5e6;
+      color: #e6a23c;
+      padding: 8px 15px;
+      font-size: 13px;
+      border-bottom: 1px solid #eee;
+    }
+    .product-list {
+      .product-row {
+        display: flex;
+        border-bottom: 1px solid #f5f5f5;
+        &:last-child {
+          border-bottom: none;
+        }
+      }
+      .product-cell {
+        padding: 15px;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+      }
+      .product-info-cell {
+        flex: 1;
+        flex-direction: row;
+        align-items: center;
+        gap: 15px;
+        .product-image {
+          width: 80px;
+          height: 80px;
+          background: #f5f5f5;
+          border-radius: 4px;
+          overflow: hidden;
+          flex-shrink: 0;
+          .el-image {
+            width: 100%;
+            height: 100%;
+          }
+          .image-placeholder {
+            width: 100%;
+            height: 100%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+          }
+        }
+        .product-detail {
+          flex: 1;
+          .product-name {
+            font-size: 14px;
+            color: #333;
+            margin-bottom: 5px;
+            line-height: 1.4;
+          }
+          .product-spec {
+            font-size: 12px;
+            color: #999;
+            margin-bottom: 5px;
+          }
+          .product-price {
+            font-size: 16px;
+            font-weight: bold;
+            color: #e60012;
+          }
+        }
+        .product-quantity {
+          font-size: 13px;
+          color: #666;
+        }
+      }
+      .amount-cell {
+        width: 120px;
+        border-left: 1px solid #f5f5f5;
+        .amount-info {
+          margin-bottom: 5px;
+          .label {
+            font-size: 12px;
+            color: #999;
+          }
+          .value {
+            font-size: 14px;
+            color: #333;
+            &.highlight {
+              font-size: 16px;
+              font-weight: bold;
+              color: #e60012;
+            }
+          }
+        }
+      }
+      .status-cell {
+        width: 120px;
+        border-left: 1px solid #f5f5f5;
+        align-items: flex-start;
+        gap: 5px;
+        .status-text {
+          font-size: 14px;
+          font-weight: 500;
+        }
+        .audit-status {
+          font-size: 12px;
+          &.success {
+            color: #e60012;
+          }
+          &.warning {
+            color: #e6a23c;
+          }
+          &.danger {
+            color: #e60012;
+          }
+        }
+      }
+      .action-cell {
+        width: 100px;
+        border-left: 1px solid #f5f5f5;
+        align-items: flex-start;
+        gap: 3px;
+      }
+    }
+  }
+}
+:deep(.table-pagination) {
+  flex-shrink: 0;
+  margin-top: 15px;
+}
 </style>

+ 107 - 24
src/views/enterprise/purchasePlan/index.vue

@@ -8,33 +8,48 @@
       <div v-for="(item, index) in planList" :key="index" class="plan-card">
         <div class="plan-image">
           <el-image :src="item.image" fit="cover">
-            <template #error><div class="image-placeholder"><el-icon :size="40"><Picture /></el-icon></div></template>
+            <template #error
+              ><div class="image-placeholder">
+                <el-icon :size="40"><Picture /></el-icon></div
+            ></template>
           </el-image>
         </div>
         <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)">了解详情 <el-icon><ArrowRight /></el-icon></div>
+          <div class="plan-link" @click="handleDetail(item)">
+            了解详情 <el-icon><ArrowRight /></el-icon>
+          </div>
         </div>
       </div>
     </div>
     <el-empty v-if="planList.length === 0" description="暂无采购方案" />
     <!-- 分页 -->
-    <TablePagination v-if="planList.length > 0" v-model:page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" :total="total" @change="handleQuery" />
+    <TablePagination
+      v-if="planList.length > 0"
+      v-model:page="queryParams.pageNum"
+      v-model:page-size="queryParams.pageSize"
+      :total="total"
+      @change="handleQuery"
+    />
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch } from 'vue'
-import { Picture, ArrowRight } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
-import { PageTitle, StatusTabs, TablePagination } from '@/components'
+import { ref, reactive, watch } from 'vue';
+import { Picture, ArrowRight } from '@element-plus/icons-vue';
+import { ElMessage } from 'element-plus';
+import { PageTitle, StatusTabs, TablePagination } from '@/components';
 
-const activeTab = ref('purchase')
-const tabs = [{ key: 'purchase', label: '采购方案' }, { key: 'exclusive', label: '专属采购方案' }, { key: 'collection', label: '收藏的采购方案' }]
-const queryParams = reactive({ pageNum: 1, pageSize: 10 })
-const total = ref(500)
-const planList = ref<any[]>([])
+const activeTab = ref('purchase');
+const tabs = [
+  { key: 'purchase', label: '采购方案' },
+  { key: 'exclusive', label: '专属采购方案' },
+  { key: 'collection', label: '收藏的采购方案' }
+];
+const queryParams = reactive({ pageNum: 1, pageSize: 10 });
+const total = ref(500);
+const planList = ref<any[]>([]);
 
 const loadData = () => {
   if (activeTab.value === 'purchase') {
@@ -42,23 +57,91 @@ const loadData = () => {
       { id: 1, name: '2025中秋福利 企业团购方案', description: '千款好礼·百大品牌·个性定制', image: '' },
       { id: 2, name: '高效会议 一屏搞定', description: '视频会议大屏解决方案,送货上门,免费安装', image: '' },
       { id: 3, name: '高效会议 一屏搞定', description: '视频会议大屏解决方案,送货上门,免费安装', image: '' }
-    ]
-    total.value = 500
+    ];
+    total.value = 500;
   } else if (activeTab.value === 'exclusive') {
-    planList.value = [{ id: 1, name: '专属定制方案A', description: '根据企业需求定制的专属采购方案', image: '' }]
-    total.value = 1
+    planList.value = [{ id: 1, name: '专属定制方案A', description: '根据企业需求定制的专属采购方案', image: '' }];
+    total.value = 1;
   } else {
-    planList.value = [{ id: 1, name: '收藏的方案', description: '您收藏的采购方案', image: '' }]
-    total.value = 1
+    planList.value = [{ id: 1, name: '收藏的方案', description: '您收藏的采购方案', image: '' }];
+    total.value = 1;
   }
-}
+};
 
-watch(activeTab, () => { queryParams.pageNum = 1; loadData() }, { immediate: true })
-const handleQuery = () => { loadData() }
-const handleDetail = (item: any) => { ElMessage.info('查看方案详情:' + item.name) }
+watch(
+  activeTab,
+  () => {
+    queryParams.pageNum = 1;
+    loadData();
+  },
+  { immediate: true }
+);
+const handleQuery = () => {
+  loadData();
+};
+const handleDetail = (item: any) => {
+  ElMessage.info('查看方案详情:' + item.name);
+};
 </script>
 
 <style scoped lang="scss">
-.plan-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
-.plan-card { border-radius: 8px; overflow: hidden; background: #fff; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); .plan-image { height: 180px; background: #f5f5f5; .el-image { width: 100%; height: 100%; } .image-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #ffe4c4 0%, #ffd4a3 100%); color: #e60012; } } .plan-info { padding: 15px; .plan-name { font-size: 15px; font-weight: 500; color: #333; margin-bottom: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .plan-desc { font-size: 13px; color: #999; margin-bottom: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .plan-link { display: flex; align-items: center; gap: 5px; font-size: 13px; color: #e60012; cursor: pointer; &:hover { text-decoration: underline; } } } }
+.plan-grid {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 20px;
+}
+.plan-card {
+  border-radius: 8px;
+  overflow: hidden;
+  background: #fff;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+  .plan-image {
+    height: 180px;
+    background: #f5f5f5;
+    .el-image {
+      width: 100%;
+      height: 100%;
+    }
+    .image-placeholder {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      background: linear-gradient(135deg, #ffe4c4 0%, #ffd4a3 100%);
+      color: #e60012;
+    }
+  }
+  .plan-info {
+    padding: 15px;
+    .plan-name {
+      font-size: 15px;
+      font-weight: 500;
+      color: #333;
+      margin-bottom: 8px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+    .plan-desc {
+      font-size: 13px;
+      color: #999;
+      margin-bottom: 10px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+    .plan-link {
+      display: flex;
+      align-items: center;
+      gap: 5px;
+      font-size: 13px;
+      color: #e60012;
+      cursor: pointer;
+      &:hover {
+        text-decoration: underline;
+      }
+    }
+  }
+}
 </style>

+ 82 - 64
src/views/enterprise/securitySetting/changePhone.vue

@@ -108,33 +108,33 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive } from 'vue'
-import { Check, CircleCheckFilled } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
+import { ref, reactive } from 'vue';
+import { Check, CircleCheckFilled } from '@element-plus/icons-vue';
+import { ElMessage } from 'element-plus';
 
-const currentStep = ref(1)
+const currentStep = ref(1);
 
 // 步骤1表单
-const step1FormRef = ref()
+const step1FormRef = ref();
 const step1Form = reactive({
   code: '',
   verified: false
-})
+});
 
-const countdown = ref(0)
+const countdown = ref(0);
 
 const step1Rules = {
   code: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
-}
+};
 
 // 步骤2表单
-const step2FormRef = ref()
+const step2FormRef = ref();
 const step2Form = reactive({
   newPhone: '',
   newCode: ''
-})
+});
 
-const newCountdown = ref(0)
+const newCountdown = ref(0);
 
 const step2Rules = {
   newPhone: [
@@ -142,58 +142,58 @@ const step2Rules = {
     { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
   ],
   newCode: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
-}
+};
 
 // 发送验证码(旧手机)
 const handleSendCode = () => {
-  countdown.value = 60
+  countdown.value = 60;
   const timer = setInterval(() => {
-    countdown.value--
+    countdown.value--;
     if (countdown.value <= 0) {
-      clearInterval(timer)
+      clearInterval(timer);
     }
-  }, 1000)
-  ElMessage.success('验证码已发送')
-}
+  }, 1000);
+  ElMessage.success('验证码已发送');
+};
 
 // 发送验证码(新手机)
 const handleSendNewCode = () => {
   if (!step2Form.newPhone) {
-    ElMessage.warning('请先输入新手机号码')
-    return
+    ElMessage.warning('请先输入新手机号码');
+    return;
   }
-  newCountdown.value = 60
+  newCountdown.value = 60;
   const timer = setInterval(() => {
-    newCountdown.value--
+    newCountdown.value--;
     if (newCountdown.value <= 0) {
-      clearInterval(timer)
+      clearInterval(timer);
     }
-  }, 1000)
-  ElMessage.success('验证码已发送')
-}
+  }, 1000);
+  ElMessage.success('验证码已发送');
+};
 
 // 下一步
 const handleNextStep = async () => {
-  const valid = await step1FormRef.value?.validate().catch(() => false)
-  if (!valid) return
-  
+  const valid = await step1FormRef.value?.validate().catch(() => false);
+  if (!valid) return;
+
   if (!step1Form.verified) {
-    ElMessage.warning('请先点击验证')
-    return
+    ElMessage.warning('请先点击验证');
+    return;
   }
-  
-  currentStep.value = 2
-}
+
+  currentStep.value = 2;
+};
 
 // 提交
 const handleSubmit = async () => {
-  const valid = await step2FormRef.value?.validate().catch(() => false)
-  if (!valid) return
-  
+  const valid = await step2FormRef.value?.validate().catch(() => false);
+  if (!valid) return;
+
   // TODO: 调用更换手机接口
-  ElMessage.success('手机号码更换成功')
-  currentStep.value = 3
-}
+  ElMessage.success('手机号码更换成功');
+  currentStep.value = 3;
+};
 </script>
 
 <style scoped lang="scss">
@@ -210,13 +210,24 @@ const handleSubmit = async () => {
   color: #666;
   background: #fff;
   border-bottom: 1px solid #eee;
-  
+
   .breadcrumb-item {
     cursor: pointer;
-    &:hover { color: #e60012; }
-    &.active { color: #333; cursor: default; &:hover { color: #333; } }
+    &:hover {
+      color: #e60012;
+    }
+    &.active {
+      color: #333;
+      cursor: default;
+      &:hover {
+        color: #333;
+      }
+    }
+  }
+  .separator {
+    margin: 0 8px;
+    color: #999;
   }
-  .separator { margin: 0 8px; color: #999; }
 }
 
 // 主体内容
@@ -248,7 +259,7 @@ const handleSubmit = async () => {
   display: flex;
   flex-direction: column;
   align-items: center;
-  
+
   .step-circle {
     width: 32px;
     height: 32px;
@@ -262,27 +273,31 @@ const handleSubmit = async () => {
     color: #999;
     margin-bottom: 10px;
   }
-  
+
   .step-label {
     font-size: 14px;
     color: #999;
   }
-  
+
   &.active {
     .step-circle {
       border-color: #52c41a;
       color: #52c41a;
     }
-    .step-label { color: #52c41a; }
+    .step-label {
+      color: #52c41a;
+    }
   }
-  
+
   &.done {
     .step-circle {
       border-color: #52c41a;
       background: #52c41a;
       color: #fff;
     }
-    .step-label { color: #52c41a; }
+    .step-label {
+      color: #52c41a;
+    }
   }
 }
 
@@ -292,8 +307,10 @@ const handleSubmit = async () => {
   background: #ddd;
   margin: 0 20px;
   margin-bottom: 30px;
-  
-  &.active { background: #52c41a; }
+
+  &.active {
+    background: #52c41a;
+  }
 }
 
 // 步骤内容
@@ -306,7 +323,7 @@ const handleSubmit = async () => {
   font-size: 16px;
   color: #333;
   margin-bottom: 10px;
-  
+
   .phone-number {
     font-weight: 500;
   }
@@ -320,23 +337,24 @@ const handleSubmit = async () => {
 }
 
 // 表单
-.verify-form, .phone-form {
+.verify-form,
+.phone-form {
   max-width: 400px;
   margin: 0 auto;
-  
+
   .code-input {
     width: 180px;
   }
-  
+
   .form-input {
     width: 280px;
   }
-  
+
   .send-code-btn {
     margin-left: 15px;
     color: #52c41a;
   }
-  
+
   .verify-checkbox {
     :deep(.el-form-item__content) {
       justify-content: center;
@@ -350,7 +368,7 @@ const handleSubmit = async () => {
   justify-content: center;
   margin-top: 30px;
   gap: 15px;
-  
+
   .next-btn {
     width: 200px;
     height: 40px;
@@ -364,14 +382,14 @@ const handleSubmit = async () => {
   background: #fffbe6;
   border: 1px solid #ffe58f;
   border-radius: 4px;
-  
+
   .tips-title {
     font-size: 14px;
     font-weight: 500;
     color: #d48806;
     margin-bottom: 10px;
   }
-  
+
   .tips-list {
     margin: 0;
     padding-left: 20px;
@@ -387,18 +405,18 @@ const handleSubmit = async () => {
   flex-direction: column;
   align-items: center;
   padding: 40px 0;
-  
+
   .success-icon {
     margin-bottom: 20px;
   }
-  
+
   .success-title {
     font-size: 20px;
     font-weight: 500;
     color: #333;
     margin-bottom: 10px;
   }
-  
+
   .success-desc {
     font-size: 14px;
     color: #999;

+ 151 - 34
src/views/enterprise/securitySetting/index.vue

@@ -89,57 +89,174 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive } from 'vue'
-import { useRouter } from 'vue-router'
-import { ArrowLeft, OfficeBuilding, Grid } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
+import { ref, reactive } from 'vue';
+import { useRouter } from 'vue-router';
+import { ArrowLeft, OfficeBuilding, Grid } from '@element-plus/icons-vue';
+import { ElMessage } from 'element-plus';
 
-const router = useRouter()
+const router = useRouter();
 
-const companyData = reactive({ companyName: '中国南方电网', role: '采购负责人' })
-const securityData = reactive({ phone: '180****7722' })
+const companyData = reactive({ companyName: '中国南方电网', role: '采购负责人' });
+const securityData = reactive({ phone: '180****7722' });
 
-const passwordDialogVisible = ref(false)
-const passwordFormRef = ref()
-const passwordForm = reactive({ oldPassword: '', newPassword: '', confirmPassword: '' })
+const passwordDialogVisible = ref(false);
+const passwordFormRef = ref();
+const passwordForm = reactive({ oldPassword: '', newPassword: '', confirmPassword: '' });
 
 const validateConfirmPassword = (_rule: any, value: string, callback: any) => {
   if (value !== passwordForm.newPassword) {
-    callback(new Error('两次输入的密码不一致'))
+    callback(new Error('两次输入的密码不一致'));
   } else {
-    callback()
+    callback();
   }
-}
+};
 
 const passwordRules = {
   oldPassword: [{ required: true, message: '请输入原密码', trigger: 'blur' }],
-  newPassword: [{ required: true, message: '请输入新密码', trigger: 'blur' }, { min: 6, max: 20, message: '密码长度为6-20位', trigger: 'blur' }],
-  confirmPassword: [{ required: true, message: '请再次输入新密码', trigger: 'blur' }, { validator: validateConfirmPassword, trigger: 'blur' }]
-}
+  newPassword: [
+    { required: true, message: '请输入新密码', trigger: 'blur' },
+    { min: 6, max: 20, message: '密码长度为6-20位', trigger: 'blur' }
+  ],
+  confirmPassword: [
+    { required: true, message: '请再次输入新密码', trigger: 'blur' },
+    { validator: validateConfirmPassword, trigger: 'blur' }
+  ]
+};
 
-const phoneDialogVisible = ref(false)
-const phoneFormRef = ref()
-const phoneForm = reactive({ phone: '', code: '' })
-const countdown = ref(0)
+const phoneDialogVisible = ref(false);
+const phoneFormRef = ref();
+const phoneForm = reactive({ phone: '', code: '' });
+const countdown = ref(0);
 
 const phoneRules = {
-  phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }, { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }],
+  phone: [
+    { required: true, message: '请输入手机号', trigger: 'blur' },
+    { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
+  ],
   code: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
-}
+};
 
-const handleBack = () => { router.push('/enterprise/companyInfo') }
-const handleModifyPassword = () => { router.push('/enterprise/securitySetting/resetPassword') }
-const handleSavePassword = async () => { const valid = await passwordFormRef.value?.validate(); if (!valid) return; ElMessage.success('密码修改成功'); passwordDialogVisible.value = false }
-const handleChangePhone = () => { router.push('/enterprise/securitySetting/changePhone') }
-const handleSendCode = () => { if (!phoneForm.phone) { ElMessage.warning('请先输入手机号'); return }; countdown.value = 60; const timer = setInterval(() => { countdown.value--; if (countdown.value <= 0) clearInterval(timer) }, 1000); ElMessage.success('验证码已发送') }
-const handleSavePhone = async () => { const valid = await phoneFormRef.value?.validate(); if (!valid) return; ElMessage.success('手机号修改成功'); phoneDialogVisible.value = false }
+const handleBack = () => {
+  router.push('/enterprise/companyInfo');
+};
+const handleModifyPassword = () => {
+  router.push('/enterprise/securitySetting/resetPassword');
+};
+const handleSavePassword = async () => {
+  const valid = await passwordFormRef.value?.validate();
+  if (!valid) return;
+  ElMessage.success('密码修改成功');
+  passwordDialogVisible.value = false;
+};
+const handleChangePhone = () => {
+  router.push('/enterprise/securitySetting/changePhone');
+};
+const handleSendCode = () => {
+  if (!phoneForm.phone) {
+    ElMessage.warning('请先输入手机号');
+    return;
+  }
+  countdown.value = 60;
+  const timer = setInterval(() => {
+    countdown.value--;
+    if (countdown.value <= 0) clearInterval(timer);
+  }, 1000);
+  ElMessage.success('验证码已发送');
+};
+const handleSavePhone = async () => {
+  const valid = await phoneFormRef.value?.validate();
+  if (!valid) return;
+  ElMessage.success('手机号修改成功');
+  phoneDialogVisible.value = false;
+};
 </script>
 
 <style scoped lang="scss">
-.security-setting-container { background: #f5f5f5; min-height: 100%; width: 1200px;margin: 0 auto;}
-.page-header { background: #fff; padding: 15px 20px; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid #eee; .page-title { font-size: 16px; font-weight: bold; color: #333; } }
-.page-content { padding: 20px; }
-.company-header { display: flex; align-items: center; gap: 15px; padding: 20px; background: #fff; border-radius: 8px; margin-bottom: 15px; .company-logo { width: 50px; height: 50px; border-radius: 8px; background: #f5f5f5; display: flex; align-items: center; justify-content: center; border: 1px solid #eee; } .company-info { .company-name { font-size: 16px; font-weight: bold; color: #333; margin-bottom: 5px; } } }
-.setting-card { display: flex; align-items: center; gap: 15px; padding: 20px; background: #fff; border-radius: 8px; margin-bottom: 15px; .setting-icon { width: 40px; height: 40px; border-radius: 8px; background: #e60012; display: flex; align-items: center; justify-content: center; } .setting-content { flex: 1; .setting-title { font-size: 15px; font-weight: 500; color: #333; margin-bottom: 5px; } .setting-desc { font-size: 13px; color: #999; } } }
-.code-input { display: flex; gap: 10px; .el-input { flex: 1; } }
+.security-setting-container {
+  background: #f5f5f5;
+  min-height: 100%;
+  width: 1200px;
+  margin: 0 auto;
+}
+.page-header {
+  background: #fff;
+  padding: 15px 20px;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  border-bottom: 1px solid #eee;
+  .page-title {
+    font-size: 16px;
+    font-weight: bold;
+    color: #333;
+  }
+}
+.page-content {
+  padding: 20px;
+}
+.company-header {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  padding: 20px;
+  background: #fff;
+  border-radius: 8px;
+  margin-bottom: 15px;
+  .company-logo {
+    width: 50px;
+    height: 50px;
+    border-radius: 8px;
+    background: #f5f5f5;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border: 1px solid #eee;
+  }
+  .company-info {
+    .company-name {
+      font-size: 16px;
+      font-weight: bold;
+      color: #333;
+      margin-bottom: 5px;
+    }
+  }
+}
+.setting-card {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  padding: 20px;
+  background: #fff;
+  border-radius: 8px;
+  margin-bottom: 15px;
+  .setting-icon {
+    width: 40px;
+    height: 40px;
+    border-radius: 8px;
+    background: #e60012;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .setting-content {
+    flex: 1;
+    .setting-title {
+      font-size: 15px;
+      font-weight: 500;
+      color: #333;
+      margin-bottom: 5px;
+    }
+    .setting-desc {
+      font-size: 13px;
+      color: #999;
+    }
+  }
+}
+.code-input {
+  display: flex;
+  gap: 10px;
+  .el-input {
+    flex: 1;
+  }
+}
 </style>

+ 74 - 56
src/views/enterprise/securitySetting/resetPassword.vue

@@ -93,40 +93,40 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive } from 'vue'
-import { Check, CircleCheckFilled } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
+import { ref, reactive } from 'vue';
+import { Check, CircleCheckFilled } from '@element-plus/icons-vue';
+import { ElMessage } from 'element-plus';
 
-const currentStep = ref(1)
+const currentStep = ref(1);
 
 // 步骤1表单
-const step1FormRef = ref()
+const step1FormRef = ref();
 const step1Form = reactive({
   phone: '18062697722',
   code: '',
   verified: false
-})
+});
 
-const countdown = ref(0)
+const countdown = ref(0);
 
 const step1Rules = {
   code: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
-}
+};
 
 // 步骤2表单
-const step2FormRef = ref()
+const step2FormRef = ref();
 const step2Form = reactive({
   newPassword: '',
   confirmPassword: ''
-})
+});
 
 const validateConfirmPassword = (_rule: any, value: string, callback: any) => {
   if (value !== step2Form.newPassword) {
-    callback(new Error('两次输入的密码不一致'))
+    callback(new Error('两次输入的密码不一致'));
   } else {
-    callback()
+    callback();
   }
-}
+};
 
 const step2Rules = {
   newPassword: [
@@ -137,42 +137,42 @@ const step2Rules = {
     { required: true, message: '请再次输入新密码', trigger: 'blur' },
     { validator: validateConfirmPassword, trigger: 'blur' }
   ]
-}
+};
 
 // 发送验证码
 const handleSendCode = () => {
-  countdown.value = 60
+  countdown.value = 60;
   const timer = setInterval(() => {
-    countdown.value--
+    countdown.value--;
     if (countdown.value <= 0) {
-      clearInterval(timer)
+      clearInterval(timer);
     }
-  }, 1000)
-  ElMessage.success('验证码已发送')
-}
+  }, 1000);
+  ElMessage.success('验证码已发送');
+};
 
 // 下一步
 const handleNextStep = async () => {
-  const valid = await step1FormRef.value?.validate().catch(() => false)
-  if (!valid) return
-  
+  const valid = await step1FormRef.value?.validate().catch(() => false);
+  if (!valid) return;
+
   if (!step1Form.verified) {
-    ElMessage.warning('请先点击验证')
-    return
+    ElMessage.warning('请先点击验证');
+    return;
   }
-  
-  currentStep.value = 2
-}
+
+  currentStep.value = 2;
+};
 
 // 提交
 const handleSubmit = async () => {
-  const valid = await step2FormRef.value?.validate().catch(() => false)
-  if (!valid) return
-  
+  const valid = await step2FormRef.value?.validate().catch(() => false);
+  if (!valid) return;
+
   // TODO: 调用修改密码接口
-  ElMessage.success('密码重置成功')
-  currentStep.value = 3
-}
+  ElMessage.success('密码重置成功');
+  currentStep.value = 3;
+};
 </script>
 
 <style scoped lang="scss">
@@ -189,13 +189,24 @@ const handleSubmit = async () => {
   color: #666;
   background: #fff;
   border-bottom: 1px solid #eee;
-  
+
   .breadcrumb-item {
     cursor: pointer;
-    &:hover { color: #e60012; }
-    &.active { color: #333; cursor: default; &:hover { color: #333; } }
+    &:hover {
+      color: #e60012;
+    }
+    &.active {
+      color: #333;
+      cursor: default;
+      &:hover {
+        color: #333;
+      }
+    }
+  }
+  .separator {
+    margin: 0 8px;
+    color: #999;
   }
-  .separator { margin: 0 8px; color: #999; }
 }
 
 // 主体内容
@@ -227,7 +238,7 @@ const handleSubmit = async () => {
   display: flex;
   flex-direction: column;
   align-items: center;
-  
+
   .step-circle {
     width: 32px;
     height: 32px;
@@ -241,27 +252,31 @@ const handleSubmit = async () => {
     color: #999;
     margin-bottom: 10px;
   }
-  
+
   .step-label {
     font-size: 14px;
     color: #999;
   }
-  
+
   &.active {
     .step-circle {
       border-color: #e60012;
       color: #e60012;
     }
-    .step-label { color: #e60012; }
+    .step-label {
+      color: #e60012;
+    }
   }
-  
+
   &.done {
     .step-circle {
       border-color: #52c41a;
       background: #52c41a;
       color: #fff;
     }
-    .step-label { color: #52c41a; }
+    .step-label {
+      color: #52c41a;
+    }
   }
 }
 
@@ -271,8 +286,10 @@ const handleSubmit = async () => {
   background: #ddd;
   margin: 0 20px;
   margin-bottom: 30px;
-  
-  &.active { background: #52c41a; }
+
+  &.active {
+    background: #52c41a;
+  }
 }
 
 // 表单
@@ -280,30 +297,31 @@ const handleSubmit = async () => {
   padding: 0 60px;
 }
 
-.verify-form, .password-form {
+.verify-form,
+.password-form {
   max-width: 500px;
   margin: 0 auto;
-  
+
   .form-input {
     width: 280px;
   }
-  
+
   .code-input {
     width: 180px;
   }
-  
+
   .form-tip {
     margin-left: 15px;
     font-size: 12px;
     color: #e60012;
     cursor: pointer;
   }
-  
+
   .send-code-btn {
     margin-left: 15px;
     color: #e60012;
   }
-  
+
   .verify-checkbox {
     :deep(.el-form-item__content) {
       margin-left: 100px !important;
@@ -316,7 +334,7 @@ const handleSubmit = async () => {
   display: flex;
   justify-content: center;
   margin-top: 40px;
-  
+
   .next-btn {
     width: 200px;
     height: 40px;
@@ -329,18 +347,18 @@ const handleSubmit = async () => {
   flex-direction: column;
   align-items: center;
   padding: 40px 0;
-  
+
   .success-icon {
     margin-bottom: 20px;
   }
-  
+
   .success-title {
     font-size: 20px;
     font-weight: 500;
     color: #333;
     margin-bottom: 10px;
   }
-  
+
   .success-desc {
     font-size: 14px;
     color: #999;

+ 352 - 68
src/views/order/batchOrder/index.vue

@@ -15,7 +15,10 @@
       <div v-for="(item, index) in productList" :key="index" class="product-item">
         <div class="product-image">
           <el-image :src="item.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-info">
@@ -34,18 +37,31 @@
       </div>
     </div>
 
-    <div class="summary-info">共导入<em>{{ totalProducts }}</em>件商品,不可下单商品<em>{{ unavailableProducts }}</em>件</div>
+    <div class="summary-info">
+      共导入<em>{{ totalProducts }}</em
+      >件商品,不可下单商品<em>{{ unavailableProducts }}</em
+      >件
+    </div>
 
     <div class="address-section">
       <div class="section-header">
         <span class="section-title">收货地址</span>
         <div class="section-actions">
-          <el-button type="primary" link @click="handleConfirmAddress"><el-icon><Location /></el-icon> 确认收货地址</el-button>
-          <el-button type="primary" link @click="handleManageAddress"><el-icon><Setting /></el-icon> 管理地址</el-button>
+          <el-button type="primary" link @click="handleConfirmAddress"
+            ><el-icon><Location /></el-icon> 确认收货地址</el-button
+          >
+          <el-button type="primary" link @click="handleManageAddress"
+            ><el-icon><Setting /></el-icon> 管理地址</el-button
+          >
         </div>
       </div>
       <div class="address-list">
-        <div v-for="(addr, index) in addressList" :key="index" :class="['address-card', { active: selectedAddress === index }]" @click="selectedAddress = index">
+        <div
+          v-for="(addr, index) in addressList"
+          :key="index"
+          :class="['address-card', { active: selectedAddress === index }]"
+          @click="selectedAddress = index"
+        >
           <div class="address-detail">{{ addr.province }} {{ addr.city }} {{ addr.district }}{{ addr.detail }}</div>
           <div class="address-company">{{ addr.company }}</div>
           <div class="address-phone">{{ addr.phone }}</div>
@@ -63,99 +79,367 @@
 
     <div class="remark-section">
       <span class="label">订单备注</span>
-      <el-input v-model="orderRemark" type="textarea" :rows="3" placeholder="请输入详细地址,省市区无需重复输入" maxlength="50" show-word-limit style="flex: 1" />
+      <el-input
+        v-model="orderRemark"
+        type="textarea"
+        :rows="3"
+        placeholder="请输入详细地址,省市区无需重复输入"
+        maxlength="50"
+        show-word-limit
+        style="flex: 1"
+      />
     </div>
 
     <div class="submit-bar">
-      <div class="total-info">共<em>{{ availableProducts }}</em>件商品</div>
-      <div class="total-amount">合计: <span class="amount">¥{{ totalAmount }}</span><span class="freight">(含运费{{ freight }}元)</span></div>
+      <div class="total-info">
+        共<em>{{ availableProducts }}</em
+        >件商品
+      </div>
+      <div class="total-amount">
+        合计: <span class="amount">¥{{ totalAmount }}</span
+        ><span class="freight">(含运费{{ freight }}元)</span>
+      </div>
       <el-button type="danger" size="large" @click="handleSubmitOrder">去下单</el-button>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, computed } from 'vue'
-import { useRouter } from 'vue-router'
-import { Picture, Location, Setting, ArrowDown } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
+import { ref, computed } from 'vue';
+import { useRouter } from 'vue-router';
+import { Picture, Location, Setting, ArrowDown } from '@element-plus/icons-vue';
+import { ElMessage } from 'element-plus';
 
-const router = useRouter()
-const selectedAddress = ref(0)
-const showAllAddress = ref(false)
-const deliveryDate = ref('')
-const orderRemark = ref('')
+const router = useRouter();
+const selectedAddress = ref(0);
+const showAllAddress = ref(false);
+const deliveryDate = ref('');
+const orderRemark = ref('');
 
 const productList = ref([
-  { id: 1, image: '', name: '清华同方超越E500台式机电脑(i3-6100/4G/1T/19.5寸)', spec1: '规格02', spec2: '规格01', price: '181', quantity: 1, subtotal: '181', offShelf: false },
-  { id: 2, image: '', name: '清华同方超越E500台式机电脑(i3-6100/4G/1T/19.5寸)', spec1: '规格02', spec2: '规格01', price: '181', quantity: 1, subtotal: '181', offShelf: true },
-  { id: 3, image: '', name: '清华同方超越E500台式机电脑(i3-6100/4G/1T/19.5寸)', spec1: '规格02', spec2: '规格01', price: '181', quantity: 1, subtotal: '181', offShelf: false },
-  { id: 4, image: '', name: '清华同方超越E500台式机电脑(i3-6100/4G/1T/19.5寸)', spec1: '规格02', spec2: '规格01', price: '181', quantity: 1, subtotal: '181', offShelf: false },
-  { id: 5, image: '', name: '清华同方超越E500台式机电脑(i3-6100/4G/1T/19.5寸)', spec1: '规格02', spec2: '规格01', price: '181', quantity: 1, subtotal: '181', offShelf: true }
-])
+  {
+    id: 1,
+    image: '',
+    name: '清华同方超越E500台式机电脑(i3-6100/4G/1T/19.5寸)',
+    spec1: '规格02',
+    spec2: '规格01',
+    price: '181',
+    quantity: 1,
+    subtotal: '181',
+    offShelf: false
+  },
+  {
+    id: 2,
+    image: '',
+    name: '清华同方超越E500台式机电脑(i3-6100/4G/1T/19.5寸)',
+    spec1: '规格02',
+    spec2: '规格01',
+    price: '181',
+    quantity: 1,
+    subtotal: '181',
+    offShelf: true
+  },
+  {
+    id: 3,
+    image: '',
+    name: '清华同方超越E500台式机电脑(i3-6100/4G/1T/19.5寸)',
+    spec1: '规格02',
+    spec2: '规格01',
+    price: '181',
+    quantity: 1,
+    subtotal: '181',
+    offShelf: false
+  },
+  {
+    id: 4,
+    image: '',
+    name: '清华同方超越E500台式机电脑(i3-6100/4G/1T/19.5寸)',
+    spec1: '规格02',
+    spec2: '规格01',
+    price: '181',
+    quantity: 1,
+    subtotal: '181',
+    offShelf: false
+  },
+  {
+    id: 5,
+    image: '',
+    name: '清华同方超越E500台式机电脑(i3-6100/4G/1T/19.5寸)',
+    spec1: '规格02',
+    spec2: '规格01',
+    price: '181',
+    quantity: 1,
+    subtotal: '181',
+    offShelf: true
+  }
+]);
 
 const addressList = ref([
   { province: '广东省', city: '广州市', district: '萝岗区科学城11号', detail: '', company: '中国南方电网有限公司', phone: '18062697722' },
   { province: '广东省', city: '广州市', district: '萝岗区科学城11号', detail: '', company: '中国南方电网有限公司', phone: '18062697722' },
   { province: '广东省', city: '广州市', district: '萝岗区科学城11号', detail: '', company: '中国南方电网有限公司', phone: '18062697722' },
   { province: '广东省', city: '广州市', district: '萝岗区科学城11号', detail: '', company: '中国南方电网有限公司', phone: '18062697722' }
-])
+]);
 
-const totalProducts = computed(() => productList.value.length)
-const unavailableProducts = computed(() => productList.value.filter(p => p.offShelf).length)
-const availableProducts = computed(() => productList.value.filter(p => !p.offShelf).length)
-const totalAmount = computed(() => productList.value.filter(p => !p.offShelf).reduce((sum, p) => sum + parseFloat(p.subtotal), 0).toFixed(2))
-const freight = ref('12.00')
+const totalProducts = computed(() => productList.value.length);
+const unavailableProducts = computed(() => productList.value.filter((p) => p.offShelf).length);
+const availableProducts = computed(() => productList.value.filter((p) => !p.offShelf).length);
+const totalAmount = computed(() =>
+  productList.value
+    .filter((p) => !p.offShelf)
+    .reduce((sum, p) => sum + parseFloat(p.subtotal), 0)
+    .toFixed(2)
+);
+const freight = ref('12.00');
 
-const handleDownloadTemplate = () => { ElMessage.success('开始下载模板文件') }
-const handleImportProducts = () => { ElMessage.info('请选择要导入的文件') }
-const handleConfirmAddress = () => { ElMessage.success('已确认收货地址') }
-const handleManageAddress = () => { router.push('/easybuv') }
+const handleDownloadTemplate = () => {
+  ElMessage.success('开始下载模板文件');
+};
+const handleImportProducts = () => {
+  ElMessage.info('请选择要导入的文件');
+};
+const handleConfirmAddress = () => {
+  ElMessage.success('已确认收货地址');
+};
+const handleManageAddress = () => {
+  router.push('/easybuv');
+};
 const handleSubmitOrder = () => {
-  if (availableProducts.value === 0) { ElMessage.warning('没有可下单的商品'); return }
-  ElMessage.success('订单提交成功')
-}
+  if (availableProducts.value === 0) {
+    ElMessage.warning('没有可下单的商品');
+    return;
+  }
+  ElMessage.success('订单提交成功');
+};
 </script>
 
 <style scoped lang="scss">
-.batch-order-container { padding: 20px; background: #fff; min-height: 100%; }
-.page-title { font-size: 16px; font-weight: bold; display: flex; align-items: center; gap: 8px; margin-bottom: 20px; }
-.title-bar { display: inline-block; width: 3px; height: 16px; background: #e60012; border-radius: 2px; }
-.action-bar { display: flex; gap: 15px; margin-bottom: 20px; }
-.section-title { font-size: 14px; font-weight: 500; color: #333; margin-bottom: 15px; }
-.product-list { border: 1px solid #eee; border-radius: 4px; margin-bottom: 15px;
-  .product-item { display: flex; align-items: center; padding: 15px; border-bottom: 1px solid #f5f5f5;
-    &:last-child { border-bottom: none; }
-    .product-image { width: 60px; height: 60px; background: #f5f5f5; border-radius: 4px; overflow: hidden; flex-shrink: 0; margin-right: 15px;
-      .el-image { width: 100%; height: 100%; } .image-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
+.batch-order-container {
+  padding: 20px;
+  background: #fff;
+  min-height: 100%;
+}
+.page-title {
+  font-size: 16px;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 20px;
+}
+.title-bar {
+  display: inline-block;
+  width: 3px;
+  height: 16px;
+  background: #e60012;
+  border-radius: 2px;
+}
+.action-bar {
+  display: flex;
+  gap: 15px;
+  margin-bottom: 20px;
+}
+.section-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 15px;
+}
+.product-list {
+  border: 1px solid #eee;
+  border-radius: 4px;
+  margin-bottom: 15px;
+  .product-item {
+    display: flex;
+    align-items: center;
+    padding: 15px;
+    border-bottom: 1px solid #f5f5f5;
+    &:last-child {
+      border-bottom: none;
+    }
+    .product-image {
+      width: 60px;
+      height: 60px;
+      background: #f5f5f5;
+      border-radius: 4px;
+      overflow: hidden;
+      flex-shrink: 0;
+      margin-right: 15px;
+      .el-image {
+        width: 100%;
+        height: 100%;
+      }
+      .image-placeholder {
+        width: 100%;
+        height: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
+    }
+    .product-info {
+      flex: 1;
+      min-width: 0;
+      .product-name {
+        font-size: 13px;
+        color: #333;
+        line-height: 1.4;
+        display: -webkit-box;
+        -webkit-line-clamp: 2;
+        -webkit-box-orient: vertical;
+        overflow: hidden;
+      }
+    }
+    .product-spec-text {
+      width: 80px;
+      text-align: center;
+      font-size: 12px;
+      color: #666;
+    }
+    .product-price {
+      width: 80px;
+      text-align: center;
+      font-size: 14px;
+      color: #e60012;
+    }
+    .product-quantity {
+      width: 60px;
+      text-align: center;
+      font-size: 14px;
+      color: #333;
     }
-    .product-info { flex: 1; min-width: 0;
-      .product-name { font-size: 13px; color: #333; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
+    .product-subtotal {
+      width: 80px;
+      text-align: center;
+      font-size: 14px;
+      color: #e60012;
     }
-    .product-spec-text { width: 80px; text-align: center; font-size: 12px; color: #666; }
-    .product-price { width: 80px; text-align: center; font-size: 14px; color: #e60012; }
-    .product-quantity { width: 60px; text-align: center; font-size: 14px; color: #333; }
-    .product-subtotal { width: 80px; text-align: center; font-size: 14px; color: #e60012; }
-    .product-action { width: 80px; text-align: center; .off-shelf { color: #e60012; font-size: 13px; } }
+    .product-action {
+      width: 80px;
+      text-align: center;
+      .off-shelf {
+        color: #e60012;
+        font-size: 13px;
+      }
+    }
+  }
+}
+.summary-info {
+  text-align: right;
+  font-size: 13px;
+  color: #666;
+  margin-bottom: 25px;
+  em {
+    color: #e60012;
+    font-style: normal;
+    font-weight: bold;
   }
 }
-.summary-info { text-align: right; font-size: 13px; color: #666; margin-bottom: 25px; em { color: #e60012; font-style: normal; font-weight: bold; } }
-.address-section { margin-bottom: 25px;
-  .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; .section-actions { display: flex; gap: 15px; } }
-  .address-list { display: flex; gap: 15px; flex-wrap: wrap; margin-bottom: 10px;
-    .address-card { width: 200px; padding: 12px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; transition: all 0.2s;
-      &:hover { border-color: #e60012; } &.active { border-color: #e60012; background: #fff5f5; }
-      .address-detail { font-size: 13px; color: #333; line-height: 1.4; margin-bottom: 5px; }
-      .address-company { font-size: 12px; color: #666; margin-bottom: 3px; }
-      .address-phone { font-size: 12px; color: #999; }
+.address-section {
+  margin-bottom: 25px;
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 15px;
+    .section-actions {
+      display: flex;
+      gap: 15px;
+    }
+  }
+  .address-list {
+    display: flex;
+    gap: 15px;
+    flex-wrap: wrap;
+    margin-bottom: 10px;
+    .address-card {
+      width: 200px;
+      padding: 12px;
+      border: 1px solid #ddd;
+      border-radius: 4px;
+      cursor: pointer;
+      transition: all 0.2s;
+      &:hover {
+        border-color: #e60012;
+      }
+      &.active {
+        border-color: #e60012;
+        background: #fff5f5;
+      }
+      .address-detail {
+        font-size: 13px;
+        color: #333;
+        line-height: 1.4;
+        margin-bottom: 5px;
+      }
+      .address-company {
+        font-size: 12px;
+        color: #666;
+        margin-bottom: 3px;
+      }
+      .address-phone {
+        font-size: 12px;
+        color: #999;
+      }
     }
   }
-  .expand-btn { font-size: 13px; }
+  .expand-btn {
+    font-size: 13px;
+  }
+}
+.delivery-section {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  margin-bottom: 20px;
+  .label {
+    font-size: 14px;
+    color: #333;
+    width: 70px;
+  }
 }
-.delivery-section { display: flex; align-items: center; gap: 15px; margin-bottom: 20px; .label { font-size: 14px; color: #333; width: 70px; } }
-.remark-section { display: flex; align-items: flex-start; gap: 15px; margin-bottom: 30px; .label { font-size: 14px; color: #333; width: 70px; padding-top: 8px; } }
-.submit-bar { display: flex; justify-content: flex-end; align-items: center; gap: 20px; padding: 20px 0; border-top: 1px solid #eee; margin-top: 20px;
-  .total-info { font-size: 14px; color: #666; em { color: #e60012; font-style: normal; font-weight: bold; } }
-  .total-amount { font-size: 14px; color: #333; .amount { font-size: 20px; font-weight: bold; color: #e60012; } .freight { font-size: 12px; color: #999; margin-left: 5px; } }
+.remark-section {
+  display: flex;
+  align-items: flex-start;
+  gap: 15px;
+  margin-bottom: 30px;
+  .label {
+    font-size: 14px;
+    color: #333;
+    width: 70px;
+    padding-top: 8px;
+  }
+}
+.submit-bar {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  gap: 20px;
+  padding: 20px 0;
+  border-top: 1px solid #eee;
+  margin-top: 20px;
+  .total-info {
+    font-size: 14px;
+    color: #666;
+    em {
+      color: #e60012;
+      font-style: normal;
+      font-weight: bold;
+    }
+  }
+  .total-amount {
+    font-size: 14px;
+    color: #333;
+    .amount {
+      font-size: 20px;
+      font-weight: bold;
+      color: #e60012;
+    }
+    .freight {
+      font-size: 12px;
+      color: #999;
+      margin-left: 5px;
+    }
+  }
 }
 </style>

+ 581 - 44
src/views/organization/approvalFlow/create.vue

@@ -1,13 +1,20 @@
 <template>
   <div class="create-flow-container">
     <div class="page-header">
-      <el-button type="primary" link @click="handleBack"><el-icon><ArrowLeft /></el-icon>返回</el-button>
-      <span class="page-title">新建审批</span>
+      <el-button type="primary" link @click="handleBack"
+        ><el-icon><ArrowLeft /></el-icon>返回</el-button
+      >
+      <span class="page-title">{{ isEdit ? '编辑审批' : '新建审批' }}</span>
     </div>
 
     <div class="flow-tip">
       <i class="title-bar"></i>
       <span>最多可设置5级审批</span>
+      <div class="status-info" v-if="isEdit">
+        <el-tag :type="hasUnsavedChanges ? 'warning' : 'success'" size="small">
+          {{ hasUnsavedChanges ? '有未保存的更改' : '已保存' }}
+        </el-tag>
+      </div>
     </div>
 
     <div class="flow-form">
@@ -18,7 +25,7 @@
     </div>
 
     <!-- 流程设计器 -->
-    <div class="flow-designer">
+    <div v-loading="detailLoading" class="flow-designer">
       <!-- 流程发起节点 -->
       <div class="flow-node start-node">
         <div class="node-header">流程发起</div>
@@ -31,7 +38,9 @@
       <!-- 连接线 + 添加按钮 -->
       <div class="flow-line">
         <div class="line"></div>
-        <div class="add-btn" @click="handleAddNode"><el-icon><Plus /></el-icon></div>
+        <div class="add-btn" @click="handleAddNode">
+          <el-icon><Plus /></el-icon>
+        </div>
         <div class="line"></div>
       </div>
 
@@ -44,15 +53,20 @@
             <el-icon @click="handleDeleteNode(index)"><Delete /></el-icon>
           </div>
         </div>
-        <div class="node-content">
-          <span>审批人:{{ node.approvers }}</span>
+        <div class="node-content" @click="handleEditNode(index)" :class="{ 'missing-approver': !node.handlerId }">
+          <div class="node-info">
+            <span class="node-name">{{ node.nodeName }}</span>
+            <span class="approver-info">审批人:{{ node.handlerName || '未设置' }}</span>
+          </div>
           <el-icon><ArrowRight /></el-icon>
         </div>
 
         <!-- 连接线 -->
         <div class="flow-line">
           <div class="line"></div>
-          <div class="add-btn" @click="handleAddNodeAfter(index)"><el-icon><Plus /></el-icon></div>
+          <div class="add-btn" @click="handleAddNodeAfter(index)">
+            <el-icon><Plus /></el-icon>
+          </div>
           <div class="line"></div>
         </div>
       </div>
@@ -62,49 +76,376 @@
         <div class="node-btn">审批结束</div>
       </div>
     </div>
+
+    <!-- 操作按钮 -->
+    <div class="footer-actions">
+      <el-button @click="handleCancel">取消</el-button>
+      <el-button type="primary" :loading="submitLoading" @click="handleSave" :disabled="!flowName.trim() || approvalNodes.length === 0">
+        {{ isEdit ? '更新流程' : '创建流程' }}
+      </el-button>
+    </div>
   </div>
+
+  <!-- 选择审批人弹窗 -->
+  <el-dialog v-model="selectDialogVisible" title="选择审批人" width="500px" :close-on-click-modal="false">
+    <div class="contact-search">
+      <el-input v-model="contactSearchKey" placeholder="搜索姓名/手机号" clearable @input="handleContactSearch">
+        <template #prefix>
+          <el-icon><Search /> </el-icon>
+        </template>
+      </el-input>
+    </div>
+
+    <div v-loading="contactLoading" class="contact-list">
+      <el-empty v-if="!contactLoading && filteredContactList.length === 0" description="暂无联系人" />
+      <div
+        v-for="contact in filteredContactList"
+        :key="contact.id"
+        class="contact-item"
+        :class="{ selected: isContactSelected(contact) }"
+        @click="toggleContact(contact)"
+      >
+        <div class="contact-info">
+          <span class="contact-name">{{ contact.contactName || contact.name }}</span>
+          <span class="contact-phone">{{ contact.phone || contact.mobile }}</span>
+        </div>
+        <el-icon v-if="isContactSelected(contact)" class="check-icon"><Check /></el-icon>
+      </div>
+    </div>
+
+    <div v-if="selectedContacts.length > 0" class="selected-tags">
+      <span class="selected-label">已选:</span>
+      <el-tag v-for="contact in selectedContacts" :key="contact.id" closable size="small" @close="removeContact(contact)">
+        {{ contact.contactName || contact.name }}
+      </el-tag>
+    </div>
+
+    <template #footer>
+      <el-button @click="selectDialogVisible = false">取消</el-button>
+      <el-button type="primary" @click="handleConfirmSelect">确定</el-button>
+    </template>
+  </el-dialog>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
-import { useRouter } from 'vue-router'
-import { ArrowLeft, ArrowRight, Plus, Edit, Delete } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
+import { ref, computed, onMounted, onUnmounted } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import { ArrowLeft, ArrowRight, Plus, Edit, Delete, Search, Check } from '@element-plus/icons-vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { addOrderFlow, updateOrderFlow, getOrderFlowDetail } from '@/api/pc/enterprise/order';
+import { getContactList } from '@/api/pc/organization/index';
+import type { OrderCustomerFlowNode, OrderCustomerFlowSaveBo } from '@/api/pc/enterprise/orderTypes';
+
+const router = useRouter();
+const route = useRoute();
+
+const flowId = computed(() => (route.query.id ? route.query.id : undefined));
+const isEdit = computed(() => !!flowId.value);
+
+const flowName = ref('');
+const approvalNodes = ref<OrderCustomerFlowNode[]>([]);
+const detailLoading = ref(false);
+const submitLoading = ref(false);
+
+// 新增的响应式变量
+const originalNodes = ref<OrderCustomerFlowNode[]>([]); // 保存原始节点数据用于对比
+
+// ==================== 联系人选择弹窗 ====================
+const selectDialogVisible = ref(false);
+const contactLoading = ref(false);
+const contactList = ref<any[]>([]);
+const contactSearchKey = ref('');
+const selectedContacts = ref<any[]>([]);
+const currentEditIndex = ref<number>(-1);
+
+const filteredContactList = computed(() => {
+  const key = contactSearchKey.value.trim().toLowerCase();
+  if (!key) return contactList.value;
+  return contactList.value.filter((c) => {
+    const name = (c.contactName || c.name || '').toLowerCase();
+    const phone = (c.phone || c.mobile || '').toLowerCase();
+    return name.includes(key) || phone.includes(key);
+  });
+});
+
+const loadContactList = async () => {
+  contactLoading.value = true;
+  try {
+    const res = await getContactList();
+    contactList.value = res.rows || [];
+  } catch (e) {
+    ElMessage.error('加载联系人列表失败');
+  } finally {
+    contactLoading.value = false;
+  }
+};
+
+/** 根据 contactList 为已有节点填充 handlerName(编辑模式下后端不返回 handlerName,需前端自行匹配) */
+const populateHandlerNames = () => {
+  if (contactList.value.length === 0 || approvalNodes.value.length === 0) return;
+  approvalNodes.value.forEach((node) => {
+    if (node.handlerId && !node.handlerName) {
+      const ids = node.handlerId.split(',').filter(Boolean);
+      node.handlerName = contactList.value
+        .filter((c) => ids.includes(String(c.id)))
+        .map((c) => c.contactName || c.name)
+        .join(',');
+    }
+  });
+  // 同步更新 originalNodes,避免 handlerName 填充导致误判为"有未保存的更改"
+  originalNodes.value = JSON.parse(JSON.stringify(approvalNodes.value));
+};
+
+const handleContactSearch = () => {
+  // filteredContactList 由 computed 自动过滤,无需额外操作
+};
+
+const isContactSelected = (contact: any) => {
+  return selectedContacts.value.some((c) => c.id === contact.id);
+};
+
+const toggleContact = (contact: any) => {
+  const idx = selectedContacts.value.findIndex((c) => c.id === contact.id);
+  if (idx === -1) {
+    selectedContacts.value.push(contact);
+  } else {
+    selectedContacts.value.splice(idx, 1);
+  }
+};
+
+const removeContact = (contact: any) => {
+  const idx = selectedContacts.value.findIndex((c) => c.id === contact.id);
+  if (idx !== -1) selectedContacts.value.splice(idx, 1);
+};
+
+const handleConfirmSelect = () => {
+  if (currentEditIndex.value < 0) return;
+  const currentNode = approvalNodes.value[currentEditIndex.value];
+  // 存储处理人ID
+  currentNode.handlerId = selectedContacts.value.map((c) => String(c.id)).join(',');
+  // 存储处理人姓名(用于显示)
+  currentNode.handlerName = selectedContacts.value.map((c) => c.contactName || c.name).join(',');
+  selectDialogVisible.value = false;
+};
+
+// ==================== 流程详情加载 ====================
 
-const router = useRouter()
-const flowName = ref('')
+/** 编辑模式:加载流程详情 */
+const loadFlowDetail = async (id: number) => {
+  detailLoading.value = true;
+  try {
+    const res = await getOrderFlowDetail(id);
+    const detail = res.data;
+    flowName.value = detail?.flowName ?? '';
+    // 深拷贝节点数据,保存原始数据用于对比
+    const nodes = (detail?.flowNodes ?? []).map((node: OrderCustomerFlowNode) => ({ ...node }));
+    approvalNodes.value = nodes;
+    originalNodes.value = JSON.parse(JSON.stringify(nodes)); // 深拷贝保存原始数据
+  } catch (e) {
+    ElMessage.error('加载流程详情失败');
+  } finally {
+    detailLoading.value = false;
+  }
+};
 
-const approvalNodes = ref([
-  { approvers: '周杰伦,杨幂,党群行政人事部...' }
-])
+onMounted(async () => {
+  if (isEdit.value && flowId.value) {
+    // 并行加载流程详情和联系人列表,两者都完成后再填充 handlerName
+    await Promise.all([loadFlowDetail(flowId.value as any), loadContactList()]);
+    populateHandlerNames();
+  } else {
+    // 新建模式只需加载联系人列表
+    loadContactList();
+  }
+});
 
 const handleBack = () => {
-  router.back()
-}
+  router.back();
+};
+
+const handleCancel = async () => {
+  // 如果有未保存的更改,提示用户确认
+  if (hasUnsavedChanges.value) {
+    try {
+      await ElMessageBox.confirm('您有未保存的更改,确定要离开页面吗?', '离开确认', {
+        confirmButtonText: '确定离开',
+        cancelButtonText: '继续编辑',
+        type: 'warning'
+      });
+    } catch {
+      return; // 用户选择继续编辑
+    }
+  }
+  router.back();
+};
 
 const handleAddNode = () => {
   if (approvalNodes.value.length >= 5) {
-    ElMessage.warning('最多可设置5级审批')
-    return
+    ElMessage.warning('最多可设置5级审批');
+    return;
   }
-  approvalNodes.value.unshift({ approvers: '请选择审批人' })
-}
+  const newNode: OrderCustomerFlowNode = {
+    nodeName: `审批节点${approvalNodes.value.length + 1}`,
+    nodeType: '1', // 中间节点
+    sort: 1,
+    handlerId: '',
+    handlerName: ''
+  };
+  approvalNodes.value.unshift(newNode);
+  // 更新所有节点的顺序
+  updateNodeOrders();
+};
 
 const handleAddNodeAfter = (index: number) => {
   if (approvalNodes.value.length >= 5) {
-    ElMessage.warning('最多可设置5级审批')
-    return
+    ElMessage.warning('最多可设置5级审批');
+    return;
   }
-  approvalNodes.value.splice(index + 1, 0, { approvers: '请选择审批人' })
-}
+  const newNode: OrderCustomerFlowNode = {
+    nodeName: `审批节点${approvalNodes.value.length + 1}`,
+    nodeType: '1', // 中间节点
+    sort: index + 2,
+    handlerId: '',
+    handlerName: ''
+  };
+  approvalNodes.value.splice(index + 1, 0, newNode);
+  // 更新所有节点的顺序
+  updateNodeOrders();
+};
+
+// 新增:更新所有节点的顺序
+const updateNodeOrders = () => {
+  approvalNodes.value.forEach((node, index) => {
+    node.sort = index + 1;
+  });
+};
 
 const handleEditNode = (index: number) => {
-  ElMessage.info(`编辑第${index + 1}级审批人`)
-}
+  currentEditIndex.value = index;
+  contactSearchKey.value = '';
+  // 回显已选中的联系人(根据 handlerId 匹配)
+  const currentNode = approvalNodes.value[index];
+  if (currentNode.handlerId) {
+    const handlerIds = currentNode.handlerId.split(',').filter(Boolean);
+    selectedContacts.value = contactList.value.filter((c) => handlerIds.includes(String(c.id)));
+  } else {
+    selectedContacts.value = [];
+  }
+  selectDialogVisible.value = true;
+  // 若联系人列表未加载,则加载
+  if (contactList.value.length === 0) {
+    loadContactList();
+  }
+};
 
-const handleDeleteNode = (index: number) => {
-  approvalNodes.value.splice(index, 1)
-}
+const handleDeleteNode = async (index: number) => {
+  const node = approvalNodes.value[index];
+
+  // 如果是编辑模式且节点有ID,提示用户确认删除
+  if (isEdit.value && node.id) {
+    try {
+      await ElMessageBox.confirm(`确定要删除审批节点"${node.nodeName}"吗?此操作将永久删除该节点。`, '删除确认', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      });
+    } catch {
+      return; // 用户取消删除
+    }
+  }
+
+  approvalNodes.value.splice(index, 1);
+  // 更新所有节点的顺序
+  updateNodeOrders();
+};
+
+/** 保存流程 */
+const handleSave = async () => {
+  if (!flowName.value.trim()) {
+    ElMessage.warning('请输入流程名称');
+    return;
+  }
+
+  // 验证审批节点
+  if (approvalNodes.value.length === 0) {
+    ElMessage.warning('请至少添加一个审批节点');
+    return;
+  }
+
+  // 检查是否有未设置审批人的节点
+  const emptyApproverNodes = approvalNodes.value.filter((node) => !node.handlerId);
+  if (emptyApproverNodes.length > 0) {
+    ElMessage.warning(`存在未设置审批人的节点:${emptyApproverNodes.map((n) => n.nodeName).join('、')}`);
+    return;
+  }
+
+  // 构造保存数据
+  const saveData: OrderCustomerFlowSaveBo = {
+    flowName: flowName.value.trim(),
+    flowNodes: approvalNodes.value.map((node, index) => ({
+      ...node,
+      sort: index + 1 // 确保顺序正确
+    }))
+  };
+
+  // 编辑模式下添加ID
+  if (isEdit.value && flowId.value) {
+    saveData.id = flowId.value;
+  }
+
+  submitLoading.value = true;
+  try {
+    if (isEdit.value) {
+      await updateOrderFlow(saveData);
+      ElMessage.success('编辑成功');
+    } else {
+      await addOrderFlow(saveData);
+      ElMessage.success('新增成功');
+    }
+    router.back();
+  } catch (e: any) {
+    console.error('保存失败:', e);
+    ElMessage.error(isEdit.value ? '编辑失败' : '新增失败');
+  } finally {
+    submitLoading.value = false;
+  }
+};
+
+// 新增:检查是否有未保存的更改
+const hasUnsavedChanges = computed(() => {
+  if (!isEdit.value) return approvalNodes.value.length > 0;
+
+  // 比较节点数量
+  if (approvalNodes.value.length !== originalNodes.value.length) return true;
+
+  // 比较节点内容
+  return approvalNodes.value.some((node, index) => {
+    const originalNode = originalNodes.value[index];
+    return (
+      node.nodeName !== originalNode.nodeName ||
+      node.handlerId !== originalNode.handlerId ||
+      node.handlerName !== originalNode.handlerName ||
+      node.sort !== originalNode.sort
+    );
+  });
+});
+
+// 新增:页面离开前确认
+const handleBeforeUnload = (e: BeforeUnloadEvent) => {
+  if (hasUnsavedChanges.value) {
+    e.preventDefault();
+    e.returnValue = '';
+    return '';
+  }
+};
+
+// 监听页面卸载事件
+window.addEventListener('beforeunload', handleBeforeUnload);
+
+// 组件卸载时移除事件监听
+onUnmounted(() => {
+  window.removeEventListener('beforeunload', handleBeforeUnload);
+});
 </script>
 
 <style scoped lang="scss">
@@ -119,7 +460,11 @@ const handleDeleteNode = (index: number) => {
   align-items: center;
   gap: 10px;
   margin-bottom: 20px;
-  .page-title { font-size: 16px; font-weight: bold; color: #333; }
+  .page-title {
+    font-size: 16px;
+    font-weight: bold;
+    color: #333;
+  }
 }
 
 .flow-tip {
@@ -129,14 +474,31 @@ const handleDeleteNode = (index: number) => {
   margin-bottom: 20px;
   font-size: 14px;
   color: #333;
-  .title-bar { display: inline-block; width: 3px; height: 14px; background: #e60012; border-radius: 2px; }
+  .title-bar {
+    display: inline-block;
+    width: 3px;
+    height: 14px;
+    background: #e60012;
+    border-radius: 2px;
+  }
+  .status-info {
+    margin-left: auto;
+  }
 }
 
 .flow-form {
   margin-bottom: 30px;
   .form-item {
-    label { display: block; font-size: 14px; color: #333; margin-bottom: 8px; }
-    :deep(.el-input__wrapper) { background: #f5f5f5; box-shadow: none; }
+    label {
+      display: block;
+      font-size: 14px;
+      color: #333;
+      margin-bottom: 8px;
+    }
+    :deep(.el-input__wrapper) {
+      background: #f5f5f5;
+      box-shadow: none;
+    }
   }
 }
 
@@ -153,19 +515,177 @@ const handleDeleteNode = (index: number) => {
   overflow: hidden;
 
   &.start-node {
-    .node-header { background: #ff6b6b; color: #fff; padding: 8px 15px; font-size: 13px; }
-    .node-content { background: #fff; border: 1px solid #eee; border-top: none; padding: 12px 15px; display: flex; justify-content: space-between; align-items: center; font-size: 13px; color: #666; cursor: pointer; }
+    .node-header {
+      background: #ff6b6b;
+      color: #fff;
+      padding: 8px 15px;
+      font-size: 13px;
+    }
+    .node-content {
+      background: #fff;
+      border: 1px solid #eee;
+      border-top: none;
+      padding: 12px 15px;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      font-size: 13px;
+      color: #666;
+      cursor: pointer;
+    }
   }
 
   &.approval-node {
-    .node-header { background: #409eff; color: #fff; padding: 8px 15px; font-size: 13px; display: flex; justify-content: space-between; align-items: center;
-      .node-actions { display: flex; gap: 10px; .el-icon { cursor: pointer; &:hover { opacity: 0.8; } } }
+    .node-header {
+      background: #409eff;
+      color: #fff;
+      padding: 8px 15px;
+      font-size: 13px;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      .node-actions {
+        display: flex;
+        gap: 10px;
+        .el-icon {
+          cursor: pointer;
+          &:hover {
+            opacity: 0.8;
+          }
+        }
+      }
+    }
+    .node-content {
+      background: #fff;
+      border: 1px solid #eee;
+      border-top: none;
+      padding: 12px 15px;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      font-size: 13px;
+      color: #666;
+      cursor: pointer;
+      transition: all 0.2s;
+
+      &:hover {
+        background: #f9f9f9;
+      }
+
+      &.missing-approver {
+        border-color: #f56c6c;
+        background: #fef0f0;
+
+        .approver-info {
+          color: #f56c6c;
+        }
+      }
+
+      .node-info {
+        display: flex;
+        flex-direction: column;
+        gap: 4px;
+
+        .node-name {
+          font-weight: 500;
+          color: #333;
+        }
+
+        .approver-info {
+          font-size: 12px;
+          color: #999;
+        }
+      }
     }
-    .node-content { background: #fff; border: 1px solid #eee; border-top: none; padding: 12px 15px; display: flex; justify-content: space-between; align-items: center; font-size: 13px; color: #666; cursor: pointer; }
   }
 
   &.end-node {
-    .node-btn { background: #909399; color: #fff; padding: 10px 40px; border-radius: 4px; font-size: 13px; text-align: center; }
+    .node-btn {
+      background: #909399;
+      color: #fff;
+      padding: 10px 40px;
+      border-radius: 4px;
+      font-size: 13px;
+      text-align: center;
+    }
+  }
+}
+
+.footer-actions {
+  display: flex;
+  justify-content: center;
+  gap: 16px;
+  margin-top: 30px;
+  padding-bottom: 20px;
+}
+
+// 联系人弹窗样式
+.contact-search {
+  margin-bottom: 12px;
+}
+
+.contact-list {
+  max-height: 320px;
+  overflow-y: auto;
+  border: 1px solid #eee;
+  border-radius: 4px;
+
+  .contact-item {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 10px 14px;
+    cursor: pointer;
+    transition: background 0.2s;
+
+    &:not(:last-child) {
+      border-bottom: 1px solid #f5f5f5;
+    }
+
+    &:hover {
+      background: #f5f7fa;
+    }
+
+    &.selected {
+      background: #ecf5ff;
+    }
+
+    .contact-info {
+      display: flex;
+      flex-direction: column;
+      gap: 2px;
+
+      .contact-name {
+        font-size: 14px;
+        color: #333;
+      }
+
+      .contact-phone {
+        font-size: 12px;
+        color: #999;
+      }
+    }
+
+    .check-icon {
+      color: #409eff;
+      font-size: 16px;
+    }
+  }
+}
+
+.selected-tags {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 6px;
+  margin-top: 12px;
+  padding: 8px 10px;
+  background: #f5f7fa;
+  border-radius: 4px;
+
+  .selected-label {
+    font-size: 13px;
+    color: #666;
   }
 }
 
@@ -175,9 +695,26 @@ const handleDeleteNode = (index: number) => {
   align-items: center;
   padding: 10px 0;
 
-  .line { width: 1px; height: 20px; background: #ddd; }
-  .add-btn { width: 20px; height: 20px; border-radius: 50%; background: #409eff; color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 12px; margin: 5px 0;
-    &:hover { background: #66b1ff; }
+  .line {
+    width: 1px;
+    height: 20px;
+    background: #ddd;
+  }
+  .add-btn {
+    width: 20px;
+    height: 20px;
+    border-radius: 50%;
+    background: #409eff;
+    color: #fff;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    font-size: 12px;
+    margin: 5px 0;
+    &:hover {
+      background: #66b1ff;
+    }
   }
 }
 </style>

+ 87 - 30
src/views/organization/approvalFlow/index.vue

@@ -5,18 +5,26 @@
       <el-button type="danger" @click="handleAddFlow">新增流程</el-button>
     </div>
 
-    <el-table :data="flowList" border>
-      <el-table-column prop="id" label="序号" width="100" align="center" />
-      <el-table-column prop="name" label="流程名称" min-width="200" />
-      <el-table-column prop="status" label="流程状态" min-width="150">
+    <el-table v-loading="loading" :data="flowList" border>
+      <el-table-column prop="flowName" label="流程名称" min-width="200" />
+      <el-table-column prop="status" label="流程状态" min-width="120">
         <template #default="{ row }">
-          <span :class="['status-text', row.status === '生效' ? 'active' : '']">{{ row.status }}</span>
+          <span :class="['status-text', row.status === '1' ? 'active' : '']">
+            {{ row.status === '1' ? '生效' : '禁用' }}
+          </span>
         </template>
       </el-table-column>
-      <el-table-column label="操作" width="150" align="center">
+      <el-table-column prop="createTime" label="创建时间" min-width="180" />
+      <el-table-column label="操作" width="200" align="center">
         <template #default="{ row }">
           <el-button type="primary" link size="small" @click="handleEdit(row)">编辑</el-button>
-          <el-button type="danger" link size="small" @click="handleDisable(row)">禁用</el-button>
+          <el-button
+            :type="row.status === '1' ? 'warning' : 'success'"
+            link
+            size="small"
+            @click="handleToggleStatus(row)"
+          >{{ row.status === '1' ? '关闭' : '开启' }}</el-button>
+          <el-button type="danger" link size="small" @click="handleDisable(row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -24,37 +32,84 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
-import { useRouter } from 'vue-router'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { PageTitle } from '@/components'
+import { ref, onMounted } from 'vue';
+import { useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { PageTitle } from '@/components';
+import { getOrderFlowList, deleteOrderFlow, startOrderFlow, closeOrderFlow } from '@/api/pc/enterprise/order';
+import type { OrderCustomerFlow } from '@/api/pc/enterprise/orderTypes';
 
-const router = useRouter()
+const router = useRouter();
 
-const flowList = ref([
-  { id: 1, name: '物资采购审批', status: '生效' },
-  { id: 2, name: '流程名称', status: '生效' },
-  { id: 3, name: '测试流程', status: '生效' }
-])
+const loading = ref(false);
+const flowList = ref<OrderCustomerFlow[]>([]);
+
+/** 加载流程列表 */
+const loadFlowList = async () => {
+  loading.value = true;
+  try {
+    const res = await getOrderFlowList();
+    flowList.value = res.rows || [];
+  } catch (e) {
+    ElMessage.error('加载流程列表失败');
+  } finally {
+    loading.value = false;
+  }
+};
+
+onMounted(() => {
+  loadFlowList();
+});
 
 const handleAddFlow = () => {
-  router.push('/organization/approvalFlow/create')
-}
+  router.push('/organization/approvalFlow/create');
+};
 
-const handleEdit = (row: any) => {
-  router.push(`/organization/approvalFlow/create?id=${row.id}`)
-}
+const handleEdit = (row: OrderCustomerFlow) => {
+  router.push(`/organization/approvalFlow/create?id=${row.id}`);
+};
 
-const handleDisable = (row: any) => {
-  ElMessageBox.confirm(`确定要禁用"${row.name}"吗?`, '提示', {
+const handleToggleStatus = (row: OrderCustomerFlow) => {
+  const isActive = row.status === '1';
+  const actionText = isActive ? '关闭' : '开启';
+  ElMessageBox.confirm(`确定要${actionText}"${row.flowName}"吗?`, '提示', {
     confirmButtonText: '确定',
     cancelButtonText: '取消',
     type: 'warning'
-  }).then(() => {
-    row.status = '禁用'
-    ElMessage.success('已禁用')
-  }).catch(() => {})
-}
+  })
+    .then(async () => {
+      try {
+        if (isActive) {
+          await closeOrderFlow(row.id as number);
+        } else {
+          await startOrderFlow(row.id as number);
+        }
+        ElMessage.success(`${actionText}成功`);
+        loadFlowList();
+      } catch (e) {
+        ElMessage.error(`${actionText}失败`);
+      }
+    })
+    .catch(() => {});
+};
+
+const handleDisable = (row: OrderCustomerFlow) => {
+  ElMessageBox.confirm(`确定要删除"${row.flowName}"吗?`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      try {
+        await deleteOrderFlow(row.id!);
+        ElMessage.success('删除成功');
+        loadFlowList();
+      } catch (e) {
+        ElMessage.error('删除失败');
+      }
+    })
+    .catch(() => {});
+};
 </script>
 
 <style scoped lang="scss">
@@ -88,6 +143,8 @@ const handleDisable = (row: any) => {
 
 .status-text {
   color: #999;
-  &.active { color: #67c23a; }
+  &.active {
+    color: #67c23a;
+  }
 }
 </style>

+ 151 - 51
src/views/organization/groupEnterprise/index.vue

@@ -60,13 +60,13 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, computed } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { PageTitle } from '@/components'
+import { ref, reactive, computed } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { PageTitle } from '@/components';
 
-const dialogVisible = ref(false)
-const formRef = ref()
-const editingRow = ref<any>(null)
+const dialogVisible = ref(false);
+const formRef = ref();
+const editingRow = ref<any>(null);
 
 const formData = reactive({
   companyName: '',
@@ -75,50 +75,150 @@ const formData = reactive({
   monthLimit: 0,
   yearLimit: 0,
   creditLimit: 0
-})
+});
 
 const formRules = {
   companyName: [{ required: true, message: '请输入公司名称', trigger: 'blur' }],
   contact: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
   phone: [{ required: true, message: '请输入电话', trigger: 'blur' }]
-}
+};
 
-const dialogTitle = computed(() => editingRow.value ? '编辑关联企业' : '新建关联企业')
+const dialogTitle = computed(() => (editingRow.value ? '编辑关联企业' : '新建关联企业'));
 
 const enterpriseList = ref([
-  { id: 1, companyName: '台州古德贸易有限公司', contact: '李婷', phone: '18062433912', monthLimit: '0.00', yearLimit: '0.00', creditLimit: '0.00', remainLimit: '0.00', purchaseStatus: '' },
-  { id: 2, companyName: '上海飞索半导体有限公司', contact: '孙思达', phone: '13442197054', monthLimit: '0.00', yearLimit: '0.00', creditLimit: '0.00', remainLimit: '0.00', purchaseStatus: '' },
-  { id: 3, companyName: '荆门格林沃德新材料有限公司', contact: '赵奕艳', phone: '15179893205', monthLimit: '0.00', yearLimit: '0.00', creditLimit: '0.00', remainLimit: '0.00', purchaseStatus: '' },
-  { id: 4, companyName: '大兴科里科技有限公司', contact: '王凡玄', phone: '15858669874', monthLimit: '0.00', yearLimit: '0.00', creditLimit: '0.00', remainLimit: '0.00', purchaseStatus: '' },
-  { id: 5, companyName: '滨州宏华国际贸易有限公司', contact: '周小艺', phone: '18402944521', monthLimit: '0.00', yearLimit: '0.00', creditLimit: '0.00', remainLimit: '0.00', purchaseStatus: '' },
-  { id: 6, companyName: '广西民锐建筑工程有限公司', contact: '李书萍', phone: '13905679687', monthLimit: '0.00', yearLimit: '0.00', creditLimit: '0.00', remainLimit: '0.00', purchaseStatus: '' },
-  { id: 7, companyName: '佳木斯晶雨轩商贸有限公司', contact: '吴彦漆', phone: '18928852431', monthLimit: '0.00', yearLimit: '0.00', creditLimit: '0.00', remainLimit: '0.00', purchaseStatus: '' },
-  { id: 8, companyName: '青海诺维信企业管理有限公司', contact: '周海', phone: '15558661691', monthLimit: '0.00', yearLimit: '0.00', creditLimit: '0.00', remainLimit: '0.00', purchaseStatus: '' },
-  { id: 9, companyName: '天津坤州港发物流有限公司', contact: '冯云', phone: '15108535283', monthLimit: '0.00', yearLimit: '0.00', creditLimit: '0.00', remainLimit: '0.00', purchaseStatus: '' },
-  { id: 10, companyName: '乌兰察布清门科技有限公司', contact: '郑盈', phone: '13782318088', monthLimit: '0.00', yearLimit: '0.00', creditLimit: '0.00', remainLimit: '0.00', purchaseStatus: '' }
-])
+  {
+    id: 1,
+    companyName: '台州古德贸易有限公司',
+    contact: '李婷',
+    phone: '18062433912',
+    monthLimit: '0.00',
+    yearLimit: '0.00',
+    creditLimit: '0.00',
+    remainLimit: '0.00',
+    purchaseStatus: ''
+  },
+  {
+    id: 2,
+    companyName: '上海飞索半导体有限公司',
+    contact: '孙思达',
+    phone: '13442197054',
+    monthLimit: '0.00',
+    yearLimit: '0.00',
+    creditLimit: '0.00',
+    remainLimit: '0.00',
+    purchaseStatus: ''
+  },
+  {
+    id: 3,
+    companyName: '荆门格林沃德新材料有限公司',
+    contact: '赵奕艳',
+    phone: '15179893205',
+    monthLimit: '0.00',
+    yearLimit: '0.00',
+    creditLimit: '0.00',
+    remainLimit: '0.00',
+    purchaseStatus: ''
+  },
+  {
+    id: 4,
+    companyName: '大兴科里科技有限公司',
+    contact: '王凡玄',
+    phone: '15858669874',
+    monthLimit: '0.00',
+    yearLimit: '0.00',
+    creditLimit: '0.00',
+    remainLimit: '0.00',
+    purchaseStatus: ''
+  },
+  {
+    id: 5,
+    companyName: '滨州宏华国际贸易有限公司',
+    contact: '周小艺',
+    phone: '18402944521',
+    monthLimit: '0.00',
+    yearLimit: '0.00',
+    creditLimit: '0.00',
+    remainLimit: '0.00',
+    purchaseStatus: ''
+  },
+  {
+    id: 6,
+    companyName: '广西民锐建筑工程有限公司',
+    contact: '李书萍',
+    phone: '13905679687',
+    monthLimit: '0.00',
+    yearLimit: '0.00',
+    creditLimit: '0.00',
+    remainLimit: '0.00',
+    purchaseStatus: ''
+  },
+  {
+    id: 7,
+    companyName: '佳木斯晶雨轩商贸有限公司',
+    contact: '吴彦漆',
+    phone: '18928852431',
+    monthLimit: '0.00',
+    yearLimit: '0.00',
+    creditLimit: '0.00',
+    remainLimit: '0.00',
+    purchaseStatus: ''
+  },
+  {
+    id: 8,
+    companyName: '青海诺维信企业管理有限公司',
+    contact: '周海',
+    phone: '15558661691',
+    monthLimit: '0.00',
+    yearLimit: '0.00',
+    creditLimit: '0.00',
+    remainLimit: '0.00',
+    purchaseStatus: ''
+  },
+  {
+    id: 9,
+    companyName: '天津坤州港发物流有限公司',
+    contact: '冯云',
+    phone: '15108535283',
+    monthLimit: '0.00',
+    yearLimit: '0.00',
+    creditLimit: '0.00',
+    remainLimit: '0.00',
+    purchaseStatus: ''
+  },
+  {
+    id: 10,
+    companyName: '乌兰察布清门科技有限公司',
+    contact: '郑盈',
+    phone: '13782318088',
+    monthLimit: '0.00',
+    yearLimit: '0.00',
+    creditLimit: '0.00',
+    remainLimit: '0.00',
+    purchaseStatus: ''
+  }
+]);
 
 const handleAdd = () => {
-  editingRow.value = null
-  formData.companyName = ''
-  formData.contact = ''
-  formData.phone = ''
-  formData.monthLimit = 0
-  formData.yearLimit = 0
-  formData.creditLimit = 0
-  dialogVisible.value = true
-}
+  editingRow.value = null;
+  formData.companyName = '';
+  formData.contact = '';
+  formData.phone = '';
+  formData.monthLimit = 0;
+  formData.yearLimit = 0;
+  formData.creditLimit = 0;
+  dialogVisible.value = true;
+};
 
 const handleEdit = (row: any) => {
-  editingRow.value = row
-  formData.companyName = row.companyName
-  formData.contact = row.contact
-  formData.phone = row.phone
-  formData.monthLimit = parseFloat(row.monthLimit) || 0
-  formData.yearLimit = parseFloat(row.yearLimit) || 0
-  formData.creditLimit = parseFloat(row.creditLimit) || 0
-  dialogVisible.value = true
-}
+  editingRow.value = row;
+  formData.companyName = row.companyName;
+  formData.contact = row.contact;
+  formData.phone = row.phone;
+  formData.monthLimit = parseFloat(row.monthLimit) || 0;
+  formData.yearLimit = parseFloat(row.yearLimit) || 0;
+  formData.creditLimit = parseFloat(row.creditLimit) || 0;
+  dialogVisible.value = true;
+};
 
 const handleDelete = (row: any) => {
   ElMessageBox.confirm(`确定要删除"${row.companyName}"吗?`, '提示', {
@@ -126,17 +226,17 @@ const handleDelete = (row: any) => {
     cancelButtonText: '取消',
     type: 'warning'
   }).then(() => {
-    const index = enterpriseList.value.findIndex(item => item.id === row.id)
+    const index = enterpriseList.value.findIndex((item) => item.id === row.id);
     if (index > -1) {
-      enterpriseList.value.splice(index, 1)
+      enterpriseList.value.splice(index, 1);
     }
-    ElMessage.success('删除成功')
-  })
-}
+    ElMessage.success('删除成功');
+  });
+};
 
 const handleSubmit = async () => {
-  const valid = await formRef.value?.validate()
-  if (!valid) return
+  const valid = await formRef.value?.validate();
+  if (!valid) return;
   if (editingRow.value) {
     Object.assign(editingRow.value, {
       companyName: formData.companyName,
@@ -145,8 +245,8 @@ const handleSubmit = async () => {
       monthLimit: formData.monthLimit.toFixed(2),
       yearLimit: formData.yearLimit.toFixed(2),
       creditLimit: formData.creditLimit.toFixed(2)
-    })
-    ElMessage.success('编辑成功')
+    });
+    ElMessage.success('编辑成功');
   } else {
     enterpriseList.value.push({
       id: enterpriseList.value.length + 1,
@@ -158,11 +258,11 @@ const handleSubmit = async () => {
       creditLimit: formData.creditLimit.toFixed(2),
       remainLimit: '0.00',
       purchaseStatus: ''
-    })
-    ElMessage.success('新建成功')
+    });
+    ElMessage.success('新建成功');
   }
-  dialogVisible.value = false
-}
+  dialogVisible.value = false;
+};
 </script>
 
 <style scoped lang="scss">

+ 6 - 2
src/views/organization/itemExpense/index.vue

@@ -7,9 +7,13 @@
 </template>
 
 <script setup lang="ts">
-import { PageTitle } from '@/components'
+import { PageTitle } from '@/components';
 </script>
 
 <style scoped lang="scss">
-.item-expense-container { padding: 20px; background: #fff; min-height: 100%; }
+.item-expense-container {
+  padding: 20px;
+  background: #fff;
+  min-height: 100%;
+}
 </style>

+ 9 - 6
src/views/valueAdded/maintenance/index.vue

@@ -1,10 +1,13 @@
 <template>
   <div class="maintenance-container">
     <PageTitle title="维保服务" />
-    
+
     <div class="empty-state">
       <div class="empty-icon">
-        <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 160'%3E%3Crect fill='%23f5f5f5' width='200' height='160' rx='8'/%3E%3Crect x='50' y='60' width='100' height='70' fill='%23e0e0e0' rx='4'/%3E%3Crect x='60' y='70' width='80' height='8' fill='%23ccc'/%3E%3Crect x='60' y='85' width='60' height='6' fill='%23ccc'/%3E%3Crect x='60' y='98' width='70' height='6' fill='%23ccc'/%3E%3Cpath d='M85 45 L95 55 L115 35' stroke='%23ccc' stroke-width='4' fill='none'/%3E%3C/svg%3E" alt="empty" />
+        <img
+          src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 160'%3E%3Crect fill='%23f5f5f5' width='200' height='160' rx='8'/%3E%3Crect x='50' y='60' width='100' height='70' fill='%23e0e0e0' rx='4'/%3E%3Crect x='60' y='70' width='80' height='8' fill='%23ccc'/%3E%3Crect x='60' y='85' width='60' height='6' fill='%23ccc'/%3E%3Crect x='60' y='98' width='70' height='6' fill='%23ccc'/%3E%3Cpath d='M85 45 L95 55 L115 35' stroke='%23ccc' stroke-width='4' fill='none'/%3E%3C/svg%3E"
+          alt="empty"
+        />
       </div>
       <p class="empty-text">亲!您还未体验过优易办公金牌维保服务,还在犹豫什么!金牌服务从点击开始!</p>
       <el-button type="danger" plain>加入金牌维保服务</el-button>
@@ -13,7 +16,7 @@
 </template>
 
 <script setup lang="ts">
-import { PageTitle } from '@/components'
+import { PageTitle } from '@/components';
 </script>
 
 <style scoped lang="scss">
@@ -29,18 +32,18 @@ import { PageTitle } from '@/components'
   align-items: center;
   justify-content: center;
   padding: 100px 20px;
-  
+
   .empty-icon {
     width: 200px;
     height: 160px;
     margin-bottom: 24px;
-    
+
     img {
       width: 100%;
       height: 100%;
     }
   }
-  
+
   .empty-text {
     font-size: 14px;
     color: #666;

+ 1 - 8
tsconfig.json

@@ -30,13 +30,6 @@
     "allowSyntheticDefaultImports": true, // 允许默认导入
     "forceConsistentCasingInFileNames": true // 强制在文件名中使用一致的大小写
   },
-  "include": [
-    "src/**/*.ts",
-    "src/**/*.vue",
-    "vite.config.ts",
-    "vitest.config.ts",
-    "eslint.config.ts",
-    "src/**/*.d.ts"
-  ],
+  "include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts", "vitest.config.ts", "eslint.config.ts", "src/**/*.d.ts"],
   "exclude": ["node_modules", "dist", "src/**/__tests__/*"]
 }

+ 1 - 1
vite/plugins/icons.ts

@@ -4,6 +4,6 @@ export default () => {
   return Icons({
     // 自动安装图标库
     autoInstall: true,
-    compiler: "vue3"
+    compiler: 'vue3'
   });
 };

+ 91 - 87
yoe-shop-web接口文档.md

@@ -3,8 +3,8 @@
 ## 项目概述
 
 ## 张林的交接文档。
-## 有一个问题:你记得我们写的pc接口的gateway可能会和之前的后台管理系统有冲突导致404,你得去gateway.yml文件里专门配置一下。
 
+## 有一个问题:你记得我们写的pc接口的gateway可能会和之前的后台管理系统有冲突导致404,你得去gateway.yml文件里专门配置一下。
 
 ## 技术栈
 
@@ -43,10 +43,10 @@ src/api/pc/
 
 **接口列表**:
 
-| 接口路径 | 方法 | 功能 | 函数名 |
-|---------|------|------|--------|
-| `/pc/enterprise/info` | GET | 查询企业信息 | `getEnterpriseInfo()` |
-| `/pc/enterprise/info` | PUT | 修改企业信息 | `updateEnterpriseInfo(data)` |
+| 接口路径              | 方法 | 功能         | 函数名                       |
+| --------------------- | ---- | ------------ | ---------------------------- |
+| `/pc/enterprise/info` | GET  | 查询企业信息 | `getEnterpriseInfo()`        |
+| `/pc/enterprise/info` | PUT  | 修改企业信息 | `updateEnterpriseInfo(data)` |
 
 ### 1.2 收货地址管理
 
@@ -56,14 +56,14 @@ src/api/pc/
 
 **接口列表**:
 
-| 接口路径 | 方法 | 功能 | 函数名 |
-|---------|------|------|--------|
-| `/pc/enterprise/address/list` | GET | 查询收货地址列表 | `getAddressList(params)` |
-| `/pc/enterprise/address/{id}` | GET | 查询收货地址详情 | `getAddressInfo(id)` |
-| `/pc/enterprise/address` | POST | 新增收货地址 | `addAddress(data)` |
-| `/pc/enterprise/address` | PUT | 修改收货地址 | `updateAddress(data)` |
-| `/pc/enterprise/address/{ids}` | DELETE | 删除收货地址 | `deleteAddress(ids)` |
-| `/pc/enterprise/address/default` | PUT | 设置默认地址 | `setDefaultAddress(id)` |
+| 接口路径                         | 方法   | 功能             | 函数名                   |
+| -------------------------------- | ------ | ---------------- | ------------------------ |
+| `/pc/enterprise/address/list`    | GET    | 查询收货地址列表 | `getAddressList(params)` |
+| `/pc/enterprise/address/{id}`    | GET    | 查询收货地址详情 | `getAddressInfo(id)`     |
+| `/pc/enterprise/address`         | POST   | 新增收货地址     | `addAddress(data)`       |
+| `/pc/enterprise/address`         | PUT    | 修改收货地址     | `updateAddress(data)`    |
+| `/pc/enterprise/address/{ids}`   | DELETE | 删除收货地址     | `deleteAddress(ids)`     |
+| `/pc/enterprise/address/default` | PUT    | 设置默认地址     | `setDefaultAddress(id)`  |
 
 ### 1.3 发票信息管理
 
@@ -73,13 +73,13 @@ src/api/pc/
 
 **接口列表**:
 
-| 接口路径 | 方法 | 功能 | 函数名 |
-|---------|------|------|--------|
-| `/pc/enterprise/invoice/list` | GET | 查询发票信息列表 | `getInvoiceList(params)` |
-| `/pc/enterprise/invoice/{id}` | GET | 查询发票详情 | `getInvoiceInfo(id)` |
-| `/pc/enterprise/invoice` | POST | 新增发票 | `addInvoice(data)` |
-| `/pc/enterprise/invoice` | PUT | 修改发票 | `updateInvoice(data)` |
-| `/pc/enterprise/invoice/{ids}` | DELETE | 删除发票 | `deleteInvoice(ids)` |
+| 接口路径                       | 方法   | 功能             | 函数名                   |
+| ------------------------------ | ------ | ---------------- | ------------------------ |
+| `/pc/enterprise/invoice/list`  | GET    | 查询发票信息列表 | `getInvoiceList(params)` |
+| `/pc/enterprise/invoice/{id}`  | GET    | 查询发票详情     | `getInvoiceInfo(id)`     |
+| `/pc/enterprise/invoice`       | POST   | 新增发票         | `addInvoice(data)`       |
+| `/pc/enterprise/invoice`       | PUT    | 修改发票         | `updateInvoice(data)`    |
+| `/pc/enterprise/invoice/{ids}` | DELETE | 删除发票         | `deleteInvoice(ids)`     |
 
 ### 1.4 专属服务人员
 
@@ -87,9 +87,9 @@ src/api/pc/
 
 **接口列表**:
 
-| 接口路径 | 方法 | 功能 | 函数名 |
-|---------|------|------|--------|
-| `/pc/enterprise/servicePerson/list` | GET | 查询专属服务人员 | `getServicePersons()` |
+| 接口路径                            | 方法 | 功能             | 函数名                |
+| ----------------------------------- | ---- | ---------------- | --------------------- |
+| `/pc/enterprise/servicePerson/list` | GET  | 查询专属服务人员 | `getServicePersons()` |
 
 ---
 
@@ -103,15 +103,15 @@ src/api/pc/
 
 **接口列表**:
 
-| 接口路径 | 方法 | 功能 | 函数名 |
-|---------|------|------|--------|
-| `/pc/enterprise/order/list` | GET | 查询订单列表 | `getOrderList(params)` |
-| `/pc/enterprise/order/statusStats` | GET | 查询订单状态统计 | `getOrderStatusStats()` |
-| `/pc/enterprise/order/{id}` | GET | 查询订单详情 | `getOrderInfo(id)` |
-| `/pc/enterprise/order/products` | GET | 查询订单商品明细 | `getOrderProducts(orderIds)` |
-| `/pc/enterprise/order/cancel` | PUT | 取消订单 | `cancelOrder(data)` |
-| `/pc/enterprise/order/checkStatus` | PUT | 审核订单 | `checkOrderStatus(data)` |
-| `/pc/enterprise/order/{ids}` | DELETE | 删除订单 | `deleteOrder(ids)` |
+| 接口路径                           | 方法   | 功能             | 函数名                       |
+| ---------------------------------- | ------ | ---------------- | ---------------------------- |
+| `/pc/enterprise/order/list`        | GET    | 查询订单列表     | `getOrderList(params)`       |
+| `/pc/enterprise/order/statusStats` | GET    | 查询订单状态统计 | `getOrderStatusStats()`      |
+| `/pc/enterprise/order/{id}`        | GET    | 查询订单详情     | `getOrderInfo(id)`           |
+| `/pc/enterprise/order/products`    | GET    | 查询订单商品明细 | `getOrderProducts(orderIds)` |
+| `/pc/enterprise/order/cancel`      | PUT    | 取消订单         | `cancelOrder(data)`          |
+| `/pc/enterprise/order/checkStatus` | PUT    | 审核订单         | `checkOrderStatus(data)`     |
+| `/pc/enterprise/order/{ids}`       | DELETE | 删除订单         | `deleteOrder(ids)`           |
 
 ### 2.2 售后服务
 
@@ -121,13 +121,13 @@ src/api/pc/
 
 **接口列表**:
 
-| 接口路径 | 方法 | 功能 | 函数名 |
-|---------|------|------|--------|
-| `/pc/enterprise/orderReturn/list` | GET | 查询售后申请列表 | `getOrderReturnList(params)` |
-| `/pc/enterprise/orderReturn/{id}` | GET | 查询售后详情 | `getOrderReturnInfo(id)` |
-| `/pc/enterprise/orderReturn` | POST | 新增售后申请 | `addOrderReturn(data)` |
-| `/pc/enterprise/orderReturn` | PUT | 修改售后申请 | `updateOrderReturn(data)` |
-| `/pc/enterprise/orderReturn/{ids}` | DELETE | 删除售后申请 | `deleteOrderReturn(ids)` |
+| 接口路径                           | 方法   | 功能             | 函数名                       |
+| ---------------------------------- | ------ | ---------------- | ---------------------------- |
+| `/pc/enterprise/orderReturn/list`  | GET    | 查询售后申请列表 | `getOrderReturnList(params)` |
+| `/pc/enterprise/orderReturn/{id}`  | GET    | 查询售后详情     | `getOrderReturnInfo(id)`     |
+| `/pc/enterprise/orderReturn`       | POST   | 新增售后申请     | `addOrderReturn(data)`       |
+| `/pc/enterprise/orderReturn`       | PUT    | 修改售后申请     | `updateOrderReturn(data)`    |
+| `/pc/enterprise/orderReturn/{ids}` | DELETE | 删除售后申请     | `deleteOrderReturn(ids)`     |
 
 ---
 
@@ -141,14 +141,14 @@ src/api/pc/
 
 **接口列表**:
 
-| 接口路径 | 方法 | 功能 | 函数名 |
-|---------|------|------|--------|
-| `/pc/organization/dept/tree` | GET | 查询部门树 | `getDeptTree()` |
-| `/pc/organization/dept/list` | GET | 查询部门列表 | `getDeptList(params)` |
-| `/pc/organization/dept/{id}` | GET | 查询部门详情 | `getDeptInfo(id)` |
-| `/pc/organization/dept` | POST | 新增部门 | `addDept(data)` |
-| `/pc/organization/dept` | PUT | 修改部门 | `updateDept(data)` |
-| `/pc/organization/dept/{ids}` | DELETE | 删除部门 | `deleteDept(ids)` |
+| 接口路径                      | 方法   | 功能         | 函数名                |
+| ----------------------------- | ------ | ------------ | --------------------- |
+| `/pc/organization/dept/tree`  | GET    | 查询部门树   | `getDeptTree()`       |
+| `/pc/organization/dept/list`  | GET    | 查询部门列表 | `getDeptList(params)` |
+| `/pc/organization/dept/{id}`  | GET    | 查询部门详情 | `getDeptInfo(id)`     |
+| `/pc/organization/dept`       | POST   | 新增部门     | `addDept(data)`       |
+| `/pc/organization/dept`       | PUT    | 修改部门     | `updateDept(data)`    |
+| `/pc/organization/dept/{ids}` | DELETE | 删除部门     | `deleteDept(ids)`     |
 
 ### 3.2 人员管理
 
@@ -158,14 +158,14 @@ src/api/pc/
 
 **接口列表**:
 
-| 接口路径 | 方法 | 功能 | 函数名 |
-|---------|------|------|--------|
-| `/pc/organization/contact/current` | GET | 获取当前用户信息 | `getCurrentUserInfo()` |
-| `/pc/organization/contact/list` | GET | 查询联系人列表 | `getContactList(params)` |
-| `/pc/organization/contact/{id}` | GET | 查询联系人详情 | `getContactInfo(id)` |
-| `/pc/organization/contact` | POST | 新增联系人 | `addContact(data)` |
-| `/pc/organization/contact` | PUT | 修改联系人 | `updateContact(data)` |
-| `/pc/organization/contact/{ids}` | DELETE | 删除联系人 | `deleteContact(ids)` |
+| 接口路径                           | 方法   | 功能             | 函数名                   |
+| ---------------------------------- | ------ | ---------------- | ------------------------ |
+| `/pc/organization/contact/current` | GET    | 获取当前用户信息 | `getCurrentUserInfo()`   |
+| `/pc/organization/contact/list`    | GET    | 查询联系人列表   | `getContactList(params)` |
+| `/pc/organization/contact/{id}`    | GET    | 查询联系人详情   | `getContactInfo(id)`     |
+| `/pc/organization/contact`         | POST   | 新增联系人       | `addContact(data)`       |
+| `/pc/organization/contact`         | PUT    | 修改联系人       | `updateContact(data)`    |
+| `/pc/organization/contact/{ids}`   | DELETE | 删除联系人       | `deleteContact(ids)`     |
 
 ### 3.3 角色管理
 
@@ -175,13 +175,13 @@ src/api/pc/
 
 **接口列表**:
 
-| 接口路径 | 方法 | 功能 | 函数名 |
-|---------|------|------|--------|
-| `/pc/organization/role/list` | GET | 查询角色列表 | `getRoleList(params)` |
-| `/pc/organization/role/{id}` | GET | 查询角色详情 | `getRoleInfo(id)` |
-| `/pc/organization/role` | POST | 新增角色 | `addRole(data)` |
-| `/pc/organization/role` | PUT | 修改角色 | `updateRole(data)` |
-| `/pc/organization/role/{ids}` | DELETE | 删除角色 | `deleteRole(ids)` |
+| 接口路径                      | 方法   | 功能         | 函数名                |
+| ----------------------------- | ------ | ------------ | --------------------- |
+| `/pc/organization/role/list`  | GET    | 查询角色列表 | `getRoleList(params)` |
+| `/pc/organization/role/{id}`  | GET    | 查询角色详情 | `getRoleInfo(id)`     |
+| `/pc/organization/role`       | POST   | 新增角色     | `addRole(data)`       |
+| `/pc/organization/role`       | PUT    | 修改角色     | `updateRole(data)`    |
+| `/pc/organization/role/{ids}` | DELETE | 删除角色     | `deleteRole(ids)`     |
 
 ---
 
@@ -195,26 +195,26 @@ src/api/pc/
 
 **接口列表**:
 
-| 接口路径 | 方法 | 功能 | 函数名 |
-|---------|------|------|--------|
-| `/pc/enterprise/statement/list` | GET | 查询对账单列表 | `getStatementList(params)` |
-| `/pc/enterprise/statement/{id}` | GET | 查询对账单详情 | `getStatementInfo(id)` |
-| `/pc/enterprise/statement/details` | GET | 查询对账单明细列表 | `getStatementDetails(params)` |
-| `/pc/enterprise/statement/confirm` | PUT | 确认对账单 | `confirmStatement(data)` |
-| `/pc/enterprise/statement/reject` | PUT | 驳回对账单 | `rejectStatement(data)` |
-| `/pc/enterprise/statementDetail/list` | GET | 查询对账单明细 | `getStatementDetailList(params)` |
-| `/pc/enterprise/statementProduct/list` | GET | 查询对账单商品列表 | `getStatementProductList(params)` |
-| `/pc/enterprise/statementInvoice/list` | GET | 查询对账单发票列表 | `getStatementInvoiceList(params)` |
+| 接口路径                               | 方法 | 功能               | 函数名                            |
+| -------------------------------------- | ---- | ------------------ | --------------------------------- |
+| `/pc/enterprise/statement/list`        | GET  | 查询对账单列表     | `getStatementList(params)`        |
+| `/pc/enterprise/statement/{id}`        | GET  | 查询对账单详情     | `getStatementInfo(id)`            |
+| `/pc/enterprise/statement/details`     | GET  | 查询对账单明细列表 | `getStatementDetails(params)`     |
+| `/pc/enterprise/statement/confirm`     | PUT  | 确认对账单         | `confirmStatement(data)`          |
+| `/pc/enterprise/statement/reject`      | PUT  | 驳回对账单         | `rejectStatement(data)`           |
+| `/pc/enterprise/statementDetail/list`  | GET  | 查询对账单明细     | `getStatementDetailList(params)`  |
+| `/pc/enterprise/statementProduct/list` | GET  | 查询对账单商品列表 | `getStatementProductList(params)` |
+| `/pc/enterprise/statementInvoice/list` | GET  | 查询对账单发票列表 | `getStatementInvoiceList(params)` |
 
 **对账状态说明**:
 
-| 状态值 | 状态名称 | 说明 |
-|-------|---------|------|
-| 0 | 待确认 | 后台生成对账单,等待企业确认 |
-| 1 | 待对账 | 企业可以点击确认按钮 |
-| 2 | 已对账 | 对账完成,按钮禁用 |
-| 3 | 驳回 | 对账被驳回 |
-| 4 | 作废 | 对账单作废 |
+| 状态值 | 状态名称 | 说明                         |
+| ------ | -------- | ---------------------------- |
+| 0      | 待确认   | 后台生成对账单,等待企业确认 |
+| 1      | 待对账   | 企业可以点击确认按钮         |
+| 2      | 已对账   | 对账完成,按钮禁用           |
+| 3      | 驳回     | 对账被驳回                   |
+| 4      | 作废     | 对账单作废                   |
 
 ### 4.2 开票管理
 
@@ -234,11 +234,12 @@ src/api/pc/
 
 **接口列表**:
 
-| 接口路径 | 方法 | 功能 | 函数名 |
-|---------|------|------|--------|
-| `/pc/dict/type/{dictType}` | GET | 根据字典类型查询字典数据 | `getDictByType(dictType)` |
+| 接口路径                   | 方法 | 功能                     | 函数名                    |
+| -------------------------- | ---- | ------------------------ | ------------------------- |
+| `/pc/dict/type/{dictType}` | GET  | 根据字典类型查询字典数据 | `getDictByType(dictType)` |
 
 **常用字典类型**:
+
 - `statement_status` - 对账状态
 - `invoice_issuance_status` - 开票状态
 - `payment_status` - 支付状态
@@ -249,10 +250,10 @@ src/api/pc/
 
 **接口列表**:
 
-| 接口路径 | 方法 | 功能 | 函数名 |
-|---------|------|------|--------|
-| `/pc/announcement/list` | GET | 查询平台公告列表 | `getAnnouncementList(params)` |
-| `/pc/announcement/{id}` | GET | 查询平台公告详情 | `getAnnouncementInfo(id)` |
+| 接口路径                | 方法 | 功能             | 函数名                        |
+| ----------------------- | ---- | ---------------- | ----------------------------- |
+| `/pc/announcement/list` | GET  | 查询平台公告列表 | `getAnnouncementList(params)` |
+| `/pc/announcement/{id}` | GET  | 查询平台公告详情 | `getAnnouncementInfo(id)`     |
 
 ---
 
@@ -263,11 +264,13 @@ src/api/pc/
 **后端文件**: `yoe-core/ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/pc/controller/PcStatementOrderController.java`
 
 **权限控制**:
+
 - PC 端用户只能查询自己企业的对账单
 - 只允许查询状态为 1(待对账)、2(已对账)、3(驳回) 的对账单
 - 不允许查询状态为 0(待确认)、4(作废) 的对账单
 
 **核心方法**:
+
 - `list()` - 查询对账单列表(带权限过滤)
 - `getInfo()` - 查询对账单详情(验证企业权限)
 - `confirm()` - 确认对账单(将状态从 1 改为 2)
@@ -280,12 +283,14 @@ src/api/pc/
 ### 7.1 环境变量
 
 **开发环境** (`.env.development`):
+
 ```
 VITE_APP_BASE_API=/dev-api
 VITE_APP_CONTEXT_PATH=/
 ```
 
 **代理配置** (`vite.config.ts`):
+
 ```typescript
 proxy: {
   '/dev-api': {
@@ -322,4 +327,3 @@ npm run build:prod
 ---
 
 ## 九、常见问题
-