Эх сурвалжийг харах

feat:新增用户和营销拼团国际化设置,和批量发货模板

叶静 7 цаг өмнө
parent
commit
04e4798300

+ 2 - 1
.env.development

@@ -29,7 +29,8 @@ SHEEP_API_ROUTING_ENABLED = true
 
 # 各模块域名配置
 SHEEP_API_COMMON_BASE_URL = http://124.222.152.234:8501
-SHEEP_API_MALL_BASE_URL = http://124.222.152.234:8501
+SHEEP_API_MALL_BASE_URL = http://192.168.0.105:8401
+; SHEEP_API_MALL_BASE_URL = http://124.222.152.234:8501
 SHEEP_API_CIF_BASE_URL = http://124.222.152.234:8501
 SHEEP_API_OPERATING_BASE_URL = http://124.222.152.234:8501
 

+ 8 - 12
.promptx/pouch.json

@@ -1,10 +1,10 @@
 {
-  "currentState": "role_activated_with_memory",
+  "currentState": "initialized",
   "stateHistory": [
     {
       "from": "initial",
       "command": "init",
-      "timestamp": "2025-08-21T09:54:20.538Z",
+      "timestamp": "2025-08-25T03:12:11.315Z",
       "args": [
         {
           "workingDirectory": "d:\\work\\bandhu-buy\\admin"
@@ -13,18 +13,14 @@
     },
     {
       "from": "initialized",
-      "command": "welcome",
-      "timestamp": "2025-08-21T09:54:25.021Z",
-      "args": []
-    },
-    {
-      "from": "service_discovery",
-      "command": "action",
-      "timestamp": "2025-08-21T09:54:29.854Z",
+      "command": "init",
+      "timestamp": "2025-08-25T08:16:36.354Z",
       "args": [
-        "vue3-expert"
+        {
+          "workingDirectory": "d:\\work\\bandhu-buy\\admin"
+        }
       ]
     }
   ],
-  "lastUpdated": "2025-08-21T09:54:29.880Z"
+  "lastUpdated": "2025-08-25T08:16:36.385Z"
 }

+ 11 - 11
.promptx/resource/project.registry.json

@@ -4,8 +4,8 @@
   "metadata": {
     "version": "2.0.0",
     "description": "project 级资源注册表",
-    "createdAt": "2025-08-21T09:54:20.548Z",
-    "updatedAt": "2025-08-21T09:54:20.552Z",
+    "createdAt": "2025-08-25T08:16:36.364Z",
+    "updatedAt": "2025-08-25T08:16:36.369Z",
     "resourceCount": 3
   },
   "resources": [
@@ -17,9 +17,9 @@
       "description": "执行模式,定义具体的行为模式",
       "reference": "@project://.promptx/resource/role/vue3-expert/execution/vue3-development.execution.md",
       "metadata": {
-        "createdAt": "2025-08-21T09:54:20.550Z",
-        "updatedAt": "2025-08-21T09:54:20.550Z",
-        "scannedAt": "2025-08-21T09:54:20.550Z",
+        "createdAt": "2025-08-25T08:16:36.366Z",
+        "updatedAt": "2025-08-25T08:16:36.366Z",
+        "scannedAt": "2025-08-25T08:16:36.366Z",
         "path": "role/vue3-expert/execution/vue3-development.execution.md"
       }
     },
@@ -31,9 +31,9 @@
       "description": "思维模式,指导AI的思考方式",
       "reference": "@project://.promptx/resource/role/vue3-expert/thought/vue3-thinking.thought.md",
       "metadata": {
-        "createdAt": "2025-08-21T09:54:20.551Z",
-        "updatedAt": "2025-08-21T09:54:20.551Z",
-        "scannedAt": "2025-08-21T09:54:20.551Z",
+        "createdAt": "2025-08-25T08:16:36.368Z",
+        "updatedAt": "2025-08-25T08:16:36.368Z",
+        "scannedAt": "2025-08-25T08:16:36.368Z",
         "path": "role/vue3-expert/thought/vue3-thinking.thought.md"
       }
     },
@@ -45,9 +45,9 @@
       "description": "专业角色,提供特定领域的专业能力",
       "reference": "@project://.promptx/resource/role/vue3-expert/vue3-expert.role.md",
       "metadata": {
-        "createdAt": "2025-08-21T09:54:20.551Z",
-        "updatedAt": "2025-08-21T09:54:20.551Z",
-        "scannedAt": "2025-08-21T09:54:20.551Z",
+        "createdAt": "2025-08-25T08:16:36.368Z",
+        "updatedAt": "2025-08-25T08:16:36.368Z",
+        "scannedAt": "2025-08-25T08:16:36.368Z",
         "path": "role/vue3-expert/vue3-expert.role.md"
       }
     }

+ 320 - 0
docs/api-interfaces.md

@@ -0,0 +1,320 @@
+# 系统API接口文档
+
+> 自动生成时间: 2025/8/25 09:45:10
+
+## 📋 目录
+
+1. [admin模块](#1-admin模块)
+2. [notice模块](#2-notice模块)
+3. [payConfig模块](#3-payConfig模块)
+4. [content模块](#4-content模块)
+5. [dashboard模块](#5-dashboard模块)
+6. [data模块](#6-data模块)
+7. [finance模块](#7-finance模块)
+8. [goods模块](#8-goods模块)
+9. [marketing模块](#9-marketing模块)
+10. [order模块](#10-order模块)
+11. [trade模块](#11-trade模块)
+12. [user模块](#12-user模块)
+
+---
+
+## 1. admin模块
+
+**文件路径**: `src\app\admin\api\index.js`
+
+### account
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | user/list | 获取列表 | CRUD |
+| detail | GET | user/detail | 获取详情 | CRUD |
+| add | POST | user/add | 新增 | CRUD |
+| edit | PUT | user/update | 更新 | CRUD |
+| delete | DELETE | user/delete | 删除 | CRUD |
+
+### options
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| logout | GET | admin/index/logout | 自定义接口 - logout | Custom |
+
+### options
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| rules | GET | admin/index/rules | 自定义接口 - rules | Custom |
+
+### admin
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | admin/auth/admin/list | 获取列表 | CRUD |
+| detail | GET | admin/auth/admin/detail | 获取详情 | CRUD |
+| add | POST | admin/auth/admin/add | 新增 | CRUD |
+| edit | PUT | admin/auth/admin/update | 更新 | CRUD |
+| delete | DELETE | admin/auth/admin/delete | 删除 | CRUD |
+
+### user
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | user/list | 获取列表 | CRUD |
+| detail | GET | user/detail | 获取详情 | CRUD |
+| add | POST | user/add | 新增 | CRUD |
+| edit | PUT | user/update | 更新 | CRUD |
+| delete | DELETE | user/delete | 删除 | CRUD |
+
+### role
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | role/list | 获取列表 | CRUD |
+| detail | GET | role/detail | 获取详情 | CRUD |
+| add | POST | role/add | 新增 | CRUD |
+| edit | PUT | role/update | 更新 | CRUD |
+| delete | DELETE | role/delete | 删除 | CRUD |
+
+### access
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | permission/list | 获取列表 | CRUD |
+| detail | GET | permission/detail | 获取详情 | CRUD |
+| add | POST | permission/add | 新增 | CRUD |
+| edit | PUT | permission/update | 更新 | CRUD |
+| delete | DELETE | permission/delete | 删除 | CRUD |
+
+### adminLog
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | admin/auth/adminLog/list | 获取列表 | CRUD |
+| detail | GET | admin/auth/adminLog/detail | 获取详情 | CRUD |
+| add | POST | admin/auth/adminLog/add | 新增 | CRUD |
+| edit | PUT | admin/auth/adminLog/update | 更新 | CRUD |
+| delete | DELETE | admin/auth/adminLog/delete | 删除 | CRUD |
+
+### banner
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | /mall/banner/list | 获取列表 | CRUD |
+| detail | GET | /mall/banner/detail | 获取详情 | CRUD |
+| add | POST | /mall/banner/add | 新增 | CRUD |
+| edit | PUT | /mall/banner/update | 更新 | CRUD |
+| delete | DELETE | /mall/banner/delete | 删除 | CRUD |
+
+### api
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| init | GET | admin/index/init | 自定义接口 - init | Custom |
+| loginConfig | GET | admin/index/loginConfig | 自定义接口 - loginConfig | Custom |
+| logout | GET | admin/index/logout | 自定义接口 - logout | Custom |
+| rules | GET | admin/index/rules | 自定义接口 - rules | Custom |
+
+---
+
+## 2. notice模块
+
+**文件路径**: `src\app\notice\api\index.js`
+
+### commonWord
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | chat/admin/commonWord/list | 获取列表 | CRUD |
+| detail | GET | chat/admin/commonWord/detail | 获取详情 | CRUD |
+| add | POST | chat/admin/commonWord/add | 新增 | CRUD |
+| edit | PUT | chat/admin/commonWord/update | 更新 | CRUD |
+| delete | DELETE | chat/admin/commonWord/delete | 删除 | CRUD |
+
+### customerService
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | chat/admin/customerService/list | 获取列表 | CRUD |
+| detail | GET | chat/admin/customerService/detail | 获取详情 | CRUD |
+| add | POST | chat/admin/customerService/add | 新增 | CRUD |
+| edit | PUT | chat/admin/customerService/update | 更新 | CRUD |
+| delete | DELETE | chat/admin/customerService/delete | 删除 | CRUD |
+
+### question
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | chat/admin/question/list | 获取列表 | CRUD |
+| detail | GET | chat/admin/question/detail | 获取详情 | CRUD |
+| add | POST | chat/admin/question/add | 新增 | CRUD |
+| edit | PUT | chat/admin/question/update | 更新 | CRUD |
+| delete | DELETE | chat/admin/question/delete | 删除 | CRUD |
+
+### user
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | chat/admin/user/list | 获取列表 | CRUD |
+| detail | GET | chat/admin/user/detail | 获取详情 | CRUD |
+| add | POST | chat/admin/user/add | 新增 | CRUD |
+| edit | PUT | chat/admin/user/update | 更新 | CRUD |
+| delete | DELETE | chat/admin/user/delete | 删除 | CRUD |
+
+### api
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| notificationType | GET | admin/notification/notificationType | 自定义接口 - notificationType | Custom |
+
+---
+
+## 3. payConfig模块
+
+**文件路径**: `src\app\shop\admin\config\componenets\payConfig\payConfig.service.js`
+
+---
+
+## 4. content模块
+
+**文件路径**: `src\app\shop\admin\content\content.service.js`
+
+---
+
+## 5. dashboard模块
+
+**文件路径**: `src\app\shop\admin\dashboard\dashboard.service.js`
+
+---
+
+## 6. data模块
+
+**文件路径**: `src\app\shop\admin\data\data.service.js`
+
+---
+
+## 7. finance模块
+
+**文件路径**: `src\app\shop\admin\finance\finance.service.js`
+
+### commission
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | /cif/red/envelope/list | 获取列表 | CRUD |
+| detail | GET | /cif/red/envelope/detail | 获取详情 | CRUD |
+| report | GET | /cif/red/envelope/report | 报表 | CRUD |
+
+### recharge
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | cif/recharge/record/list | 获取列表 | CRUD |
+| detail | GET | cif/recharge/record/detail | 获取详情 | CRUD |
+| report | GET | cif/recharge/record/report | 报表 | CRUD |
+
+### withdraw
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | /cif/withdraw/record/list | 获取列表 | CRUD |
+| detail | GET | /cif/withdraw/record/detail | 获取详情 | CRUD |
+| report | GET | /cif/withdraw/record/report | 报表 | CRUD |
+
+---
+
+## 8. goods模块
+
+**文件路径**: `src\app\shop\admin\goods\goods.service.js`
+
+### goods
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | mall/product/list | 获取列表 | CRUD |
+| detail | GET | mall/product/detail | 获取详情 | CRUD |
+| add | POST | mall/product/add | 新增 | CRUD |
+| edit | PUT | mall/product/update | 更新 | CRUD |
+| delete | DELETE | mall/product/delete | 删除 | CRUD |
+
+### rule
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | mall/product/rule/list | 获取列表 | CRUD |
+| detail | GET | mall/product/rule/detail | 获取详情 | CRUD |
+| add | POST | mall/product/rule/add | 新增 | CRUD |
+| edit | PUT | mall/product/rule/update | 更新 | CRUD |
+| delete | DELETE | mall/product/rule/delete | 删除 | CRUD |
+
+### category
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | mall/category/list | 获取列表 | CRUD |
+| detail | GET | mall/category/detail | 获取详情 | CRUD |
+| add | POST | mall/category/add | 新增 | CRUD |
+| edit | PUT | mall/category/update | 更新 | CRUD |
+| delete | DELETE | mall/category/delete | 删除 | CRUD |
+
+---
+
+## 9. marketing模块
+
+**文件路径**: `src\app\shop\admin\marketing\marketing.service.js`
+
+### group
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | mall/flash/activity/list | 获取列表 | CRUD |
+| detail | GET | mall/flash/activity/detail | 获取详情 | CRUD |
+| add | POST | mall/flash/activity/add | 新增 | CRUD |
+| edit | PUT | mall/flash/activity/update | 更新 | CRUD |
+| delete | DELETE | mall/flash/activity/delete | 删除 | CRUD |
+
+---
+
+## 10. order模块
+
+**文件路径**: `src\app\shop\admin\order\order.service.js`
+
+### order
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | mall/order/list | 获取列表 | CRUD |
+| detail | GET | mall/order/detail | 获取详情 | CRUD |
+| add | POST | mall/order/add | 新增 | CRUD |
+| edit | PUT | mall/order/update | 更新 | CRUD |
+| delete | DELETE | mall/order/delete | 删除 | CRUD |
+
+---
+
+## 11. trade模块
+
+**文件路径**: `src\app\shop\admin\trade\trade.service.js`
+
+---
+
+## 12. user模块
+
+**文件路径**: `src\app\shop\admin\user\user.service.js`
+
+### list
+
+| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |
+|---------|---------|---------|------|------|
+| list | POST | /cif/user/list | 获取列表 | CRUD |
+| detail | GET | /cif/user/detail | 获取详情 | CRUD |
+| edit | PUT | /cif/user/update | 更新 | CRUD |
+| delete | DELETE | /cif/user/delete | 删除 | CRUD |
+| report | GET | /cif/user/report | 报表 | CRUD |
+
+---
+
+## 📊 统计信息
+
+- **模块总数**: 12
+- **接口总数**: 101
+- **生成时间**: 2025/8/25 09:45:10
+

+ 1 - 0
package.json

@@ -11,6 +11,7 @@
     "build": "vite build",
     "prettier": "prettier --write  \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
     "preview": "vite preview",
+    "docs:api": "node scripts/generate-api-docs.mjs",
     "test": "vite build && vite preview"
   },
   "dependencies": {

+ 389 - 0
scripts/generate-api-docs.mjs

@@ -0,0 +1,389 @@
+#!/usr/bin/env node
+
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const projectRoot = path.resolve(__dirname, '..');
+
+// CRUD操作映射
+const CRUD_OPERATIONS = {
+  list: { method: 'POST', path: '/list', description: '获取列表' },
+  detail: { method: 'GET', path: '/detail', description: '获取详情' },
+  add: { method: 'POST', path: '/add', description: '新增' },
+  edit: { method: 'PUT', path: '/update', description: '更新' },
+  delete: { method: 'DELETE', path: '/delete', description: '删除' },
+  export: { method: 'GET', path: '/export', description: '导出' },
+  report: { method: 'GET', path: '/report', description: '报表' },
+};
+
+// 扫描所有service文件
+function scanServiceFiles(dir) {
+  const serviceFiles = [];
+
+  function scanDirectory(currentDir) {
+    const items = fs.readdirSync(currentDir, { withFileTypes: true });
+
+    for (const item of items) {
+      const fullPath = path.join(currentDir, item.name);
+
+      if (item.isDirectory()) {
+        scanDirectory(fullPath);
+      } else if (
+        item.name.endsWith('.service.js') ||
+        (item.name === 'index.js' && (fullPath.includes('/api/') || fullPath.includes('\\api\\')))
+      ) {
+        serviceFiles.push({
+          path: fullPath,
+          relativePath: path.relative(projectRoot, fullPath),
+          moduleName: extractModuleName(fullPath),
+        });
+      }
+    }
+  }
+
+  scanDirectory(dir);
+  return serviceFiles;
+}
+
+// 提取模块名称
+function extractModuleName(filePath) {
+  const parts = filePath.split(path.sep);
+
+  // 对于 .service.js 文件
+  const serviceIndex = parts.findIndex((part) => part.endsWith('.service.js'));
+  if (serviceIndex > 0) {
+    return parts[serviceIndex - 1];
+  }
+
+  // 对于 api/index.js 文件
+  const apiIndex = parts.findIndex((part) => part === 'api');
+  if (apiIndex > 0 && parts[apiIndex + 1] === 'index.js') {
+    // 取api目录的上一级目录作为模块名
+    return parts[apiIndex - 1];
+  }
+
+  return path.basename(filePath, '.js');
+}
+
+// 解析service文件内容
+async function parseServiceFile(filePath) {
+  try {
+    const content = fs.readFileSync(filePath, 'utf-8');
+
+    // 提取API对象 - 支持多种格式
+    let apiContent = null;
+
+    // 格式1: const api = {...};
+    let apiMatch = content.match(/const api = \{([\s\S]*?)\};/);
+    if (apiMatch) {
+      apiContent = apiMatch[1];
+    } else {
+      // 格式2: export default {...}
+      apiMatch = content.match(/export default \{([\s\S]*?)\};?$/m);
+      if (apiMatch) {
+        apiContent = apiMatch[1];
+      }
+    }
+
+    if (!apiContent) {
+      return null;
+    }
+
+    const apis = parseApiContent(apiContent, filePath);
+
+    return {
+      filePath,
+      apis,
+    };
+  } catch (error) {
+    console.error(`解析文件失败 ${filePath}:`, error.message);
+    return null;
+  }
+}
+
+// 解析API内容
+function parseApiContent(content, filePath) {
+  const apis = [];
+
+  // 首先解析嵌套的API结构
+  const nestedApiRegex = /(\w+):\s*\{([\s\S]*?)(?=\w+:\s*\{|$)/g;
+  let match;
+
+  while ((match = nestedApiRegex.exec(content)) !== null) {
+    const [, apiName, apiBody] = match;
+    // 检查是否包含CRUD或方法定义
+    if (apiBody.includes('CRUD') || apiBody.includes('=>') || apiBody.includes('request(')) {
+      const cleanApiBody = apiBody.replace(/,\s*$/, '').trim();
+      const apiInfo = parseApiBody(apiName, cleanApiBody, filePath);
+      if (apiInfo) {
+        apis.push(apiInfo);
+      }
+    }
+  }
+
+  // 同时解析顶层方法
+  const topLevelEndpoints = parseCustomEndpoints(content);
+  if (topLevelEndpoints.length > 0) {
+    const topLevelApi = {
+      name: 'api',
+      endpoints: topLevelEndpoints,
+    };
+    apis.push(topLevelApi);
+  }
+
+  return apis;
+}
+
+// 解析API主体
+function parseApiBody(apiName, body, filePath) {
+  const api = {
+    name: apiName,
+    endpoints: [],
+  };
+
+  // 检查是否使用CRUD
+  const crudMatch = body.match(/\.\.\.CRUD\(['"`]([^'"`]+)['"`](?:,\s*\[([^\]]*)\])?\)/);
+  if (crudMatch) {
+    const [, basePath, methodsStr] = crudMatch;
+    const methods = methodsStr
+      ? methodsStr.split(',').map((m) => m.trim().replace(/['"`]/g, ''))
+      : ['list', 'detail', 'add', 'edit', 'delete'];
+
+    // 添加CRUD端点
+    methods.forEach((method) => {
+      if (CRUD_OPERATIONS[method]) {
+        const operation = CRUD_OPERATIONS[method];
+        const fullUrl = basePath + operation.path;
+
+        // 过滤掉以 shop/admin/ 开头的接口
+        if (!fullUrl.startsWith('shop/admin/')) {
+          api.endpoints.push({
+            name: method,
+            method: operation.method,
+            url: fullUrl,
+            description: operation.description,
+            type: 'CRUD',
+          });
+        }
+      }
+    });
+  }
+
+  // 解析自定义端点
+  const customEndpoints = parseCustomEndpoints(body);
+  api.endpoints.push(...customEndpoints);
+
+  return api.endpoints.length > 0 ? api : null;
+}
+
+// 解析自定义端点
+function parseCustomEndpoints(body) {
+  const endpoints = [];
+
+  // 匹配自定义方法 - 支持多行格式
+  const methodPattern = /(\w+):\s*\([^)]*\)\s*=>\s*([\s\S]*?)(?=,\s*\w+:|$)/g;
+  let match;
+
+  while ((match = methodPattern.exec(body)) !== null) {
+    const [, methodName, methodBody] = match;
+
+    // 跳过已知的CRUD方法和展开操作
+    if (
+      ['list', 'detail', 'add', 'edit', 'delete', 'export', 'report'].includes(methodName) ||
+      methodBody.includes('...CRUD')
+    ) {
+      continue;
+    }
+
+    // 解析request调用
+    const requestMatch = methodBody.match(/request\(\{([\s\S]*?)\}\)/);
+    if (requestMatch) {
+      const requestConfig = requestMatch[1];
+
+      // 提取URL和方法
+      const urlMatch = requestConfig.match(/url:\s*['"`]([^'"`]+)['"`]/);
+      const methodMatch = requestConfig.match(/method:\s*['"`]([^'"`]+)['"`]/);
+
+      if (urlMatch) {
+        const url = urlMatch[1];
+
+        // 过滤掉以 shop/admin/ 开头的接口
+        if (!url.startsWith('shop/admin/')) {
+          const description = generateMethodDescription(methodName);
+
+          endpoints.push({
+            name: methodName,
+            method: methodMatch ? methodMatch[1] : 'GET',
+            url: url,
+            description,
+            type: 'Custom',
+          });
+        }
+      }
+    }
+
+    // 解析SELECT调用
+    const selectMatch = methodBody.match(/SELECT\(['"`]([^'"`]+)['"`]/);
+    if (selectMatch) {
+      const url = selectMatch[1] + '/select';
+
+      // 过滤掉以 shop/admin/ 开头的接口
+      if (!url.startsWith('shop/admin/')) {
+        endpoints.push({
+          name: methodName,
+          method: 'GET',
+          url: url,
+          description: generateMethodDescription(methodName),
+          type: 'Custom',
+        });
+      }
+    }
+  }
+
+  return endpoints;
+}
+
+// 生成方法描述
+function generateMethodDescription(methodName) {
+  const descriptions = {
+    batchShowStatus: '批量上下架商品',
+    getStatusNum: '获取商品状态数量',
+    select: '选择列表',
+    getType: '获取类型',
+    dispatch: '发货',
+    dispatchByUpload: '上传发货单发货',
+    total: '获取总览数据',
+    ranking: '获取排行数据',
+    chart: '获取图表数据',
+    getStats: '获取统计数据',
+    userDetail: '获取用户详情',
+    myUsers: '获取我的用户',
+    getUserAddresses: '获取用户地址',
+    share: '分享相关',
+    coupon: '优惠券相关',
+    couponList: '优惠券列表',
+  };
+
+  return descriptions[methodName] || `自定义接口 - ${methodName}`;
+}
+
+// 生成Markdown文档
+function generateMarkdown(serviceData) {
+  let markdown = `# 系统API接口文档\n\n`;
+  markdown += `> 自动生成时间: ${new Date().toLocaleString('zh-CN')}\n\n`;
+  markdown += `## 📋 目录\n\n`;
+
+  // 生成目录
+  serviceData.forEach((service, index) => {
+    markdown += `${index + 1}. [${service.moduleName}模块](#${index + 1}-${service.moduleName}模块)\n`;
+  });
+
+  markdown += `\n---\n\n`;
+
+  // 生成详细内容
+  serviceData.forEach((service, index) => {
+    markdown += `## ${index + 1}. ${service.moduleName}模块\n\n`;
+    markdown += `**文件路径**: \`${service.relativePath}\`\n\n`;
+
+    if (service.data && service.data.apis) {
+      service.data.apis.forEach((api) => {
+        markdown += `### ${api.name}\n\n`;
+
+        if (api.endpoints.length > 0) {
+          markdown += `| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |\n`;
+          markdown += `|---------|---------|---------|------|------|\n`;
+
+          api.endpoints.forEach((endpoint) => {
+            markdown += `| ${endpoint.name} | ${endpoint.method} | ${endpoint.url} | ${endpoint.description} | ${endpoint.type} |\n`;
+          });
+
+          markdown += `\n`;
+        }
+      });
+    }
+
+    markdown += `---\n\n`;
+  });
+
+  // 添加统计信息
+  const totalApis = serviceData.reduce((total, service) => {
+    if (service.data && service.data.apis) {
+      return (
+        total + service.data.apis.reduce((apiTotal, api) => apiTotal + api.endpoints.length, 0)
+      );
+    }
+    return total;
+  }, 0);
+
+  markdown += `## 📊 统计信息\n\n`;
+  markdown += `- **模块总数**: ${serviceData.length}\n`;
+  markdown += `- **接口总数**: ${totalApis}\n`;
+  markdown += `- **生成时间**: ${new Date().toLocaleString('zh-CN')}\n\n`;
+
+  return markdown;
+}
+
+// 主函数
+async function main() {
+  console.log('🔍 开始扫描API接口文件...\n');
+
+  // 扫描service文件
+  const srcDir = path.join(projectRoot, 'src');
+  const serviceFiles = scanServiceFiles(srcDir);
+
+  console.log(`📁 找到 ${serviceFiles.length} 个service文件:\n`);
+  serviceFiles.forEach((file) => {
+    console.log(`  - ${file.relativePath} (${file.moduleName})`);
+  });
+
+  console.log('\n🔄 开始解析API定义...\n');
+
+  // 解析每个service文件
+  const serviceData = [];
+  for (const file of serviceFiles) {
+    console.log(`正在解析: ${file.relativePath}`);
+    const data = await parseServiceFile(file.path);
+
+    serviceData.push({
+      ...file,
+      data,
+    });
+  }
+
+  console.log('\n📝 生成API文档...\n');
+
+  // 生成文档
+  const markdown = generateMarkdown(serviceData);
+
+  // 保存文档
+  const outputPath = path.join(projectRoot, 'docs', 'api-interfaces.md');
+
+  // 确保docs目录存在
+  const docsDir = path.dirname(outputPath);
+  if (!fs.existsSync(docsDir)) {
+    fs.mkdirSync(docsDir, { recursive: true });
+  }
+
+  fs.writeFileSync(outputPath, markdown, 'utf-8');
+
+  console.log(`✅ API文档生成完成!`);
+  console.log(`📄 文档路径: ${outputPath}`);
+  console.log(`📊 统计信息:`);
+  console.log(`   - 模块数量: ${serviceData.length}`);
+
+  const totalApis = serviceData.reduce((total, service) => {
+    if (service.data && service.data.apis) {
+      return (
+        total + service.data.apis.reduce((apiTotal, api) => apiTotal + api.endpoints.length, 0)
+      );
+    }
+    return total;
+  }, 0);
+
+  console.log(`   - 接口数量: ${totalApis}`);
+}
+
+main().catch(console.error);

+ 115 - 124
src/app/shop/admin/marketing/group/edit.vue

@@ -1,159 +1,150 @@
 <template>
   <el-container>
     <el-main>
-      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="130px">
-        <el-form-item label="活动名称" prop="name">
-          <el-input v-model="form.model.name" placeholder="请填写活动名称"></el-input>
+      <el-form :model="form.model" :rules="form.rules" ref="formRef" :label-width="formLabelWidth">
+        <el-form-item :label="t('modules.marketing.activityName')" prop="name">
+          <el-input v-model="form.model.name" :placeholder="t('modules.marketing.enterActivityName')"></el-input>
         </el-form-item>
 
-        <el-form-item label="开始时间" prop="startTime">
-          <el-date-picker
-            v-model="form.model.startTime"
-            type="datetime"
-            placeholder="请选择开始时间"
-            format="YYYY-MM-DD HH:mm:ss"
-            value-format="YYYY-MM-DD HH:mm:ss"
-            style="width: 100%"
-          />
+        <el-form-item :label="t('modules.marketing.startTime')" prop="startTime">
+          <el-date-picker v-model="form.model.startTime" type="datetime"
+            :placeholder="t('modules.marketing.selectStartTime')" format="YYYY-MM-DD HH:mm:ss"
+            value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
         </el-form-item>
 
-        <el-form-item label="结束时间" prop="endTime">
-          <el-date-picker
-            v-model="form.model.endTime"
-            type="datetime"
-            placeholder="请选择结束时间"
-            format="YYYY-MM-DD HH:mm:ss"
-            value-format="YYYY-MM-DD HH:mm:ss"
-            style="width: 100%"
-          />
+        <el-form-item :label="t('modules.marketing.endTime')" prop="endTime">
+          <el-date-picker v-model="form.model.endTime" type="datetime"
+            :placeholder="t('modules.marketing.selectEndTime')" format="YYYY-MM-DD HH:mm:ss"
+            value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
         </el-form-item>
 
-        <el-form-item label="成团默认人数" prop="content.groupNumber">
-          <el-select
-            v-model="form.model.content.groupNumber"
-            placeholder="请选择成团默认人数"
-            style="width: 100%"
-          >
-            <el-option label="5人团" :value="5"></el-option>
-            <el-option label="10人团" :value="10"></el-option>
-            <el-option label="20人团" :value="20"></el-option>
+        <el-form-item :label="t('modules.marketing.defaultGroupSize')" prop="content.groupNumber">
+          <el-select v-model="form.model.content.groupNumber" :placeholder="t('modules.marketing.selectGroupSize')"
+            style="width: 100%">
+            <el-option :label="t('modules.marketing.fivePersonGroup')" :value="5"></el-option>
+            <el-option :label="t('modules.marketing.tenPersonGroup')" :value="10"></el-option>
+            <el-option :label="t('modules.marketing.twentyPersonGroup')" :value="20"></el-option>
           </el-select>
         </el-form-item>
 
-        <el-form-item label="开团倒计时结束" prop="content.countdownTime">
-          <el-select
-            v-model="form.model.content.countdownTime"
-            placeholder="请选择开团倒计时结束"
-            style="width: 100%"
-          >
-            <el-option label="12小时" :value="12"></el-option>
-            <el-option label="24小时" :value="24"></el-option>
-            <el-option label="48小时" :value="48"></el-option>
-            <el-option label="72小时" :value="72"></el-option>
-            <el-option label="168小时" :value="168"></el-option>
+        <el-form-item :label="t('modules.marketing.countdownEnd')" prop="content.countdownTime">
+          <el-select v-model="form.model.content.countdownTime"
+            :placeholder="t('modules.marketing.selectCountdownTime')" style="width: 100%">
+            <el-option :label="t('modules.marketing.twelveHours')" :value="12"></el-option>
+            <el-option :label="t('modules.marketing.twentyFourHours')" :value="24"></el-option>
+            <el-option :label="t('modules.marketing.fortyEightHours')" :value="48"></el-option>
+            <el-option :label="t('modules.marketing.seventyTwoHours')" :value="72"></el-option>
+            <el-option :label="t('modules.marketing.oneWeek')" :value="168"></el-option>
           </el-select>
-          <div class="form-tip mx-10px">开团后倒计时多久结束拼团活动</div>
+          <div class="form-tip mx-10px">{{ t('modules.marketing.countdownTip') }}</div>
         </el-form-item>
       </el-form>
     </el-main>
     <el-footer class="sa-footer--submit">
-      <el-button v-if="modal.params.type == 'add'" type="primary" @click="confirm">保存</el-button>
-      <el-button v-if="modal.params.type == 'edit'" type="primary" @click="confirm">更新</el-button>
+      <el-button v-if="modal.params.type == 'add'" type="primary" @click="confirm">{{ t('common.save') }}</el-button>
+      <el-button v-if="modal.params.type == 'edit'" type="primary" @click="confirm">{{ t('common.update') }}</el-button>
     </el-footer>
   </el-container>
 </template>
 <script setup>
-  import { cloneDeep } from 'lodash';
-  import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from '../marketing.service';
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
-  // 添加 编辑 form
-  let formRef = ref(null);
-  const form = reactive({
-    model: {
-      name: '',
-      startTime: '',
-      endTime: '',
-      content: {
-        groupNumber: '',
-        countdownTime: '',
-      },
-    },
-    rules: {
-      name: [{ required: true, message: '请填写活动名称', trigger: 'blur' }],
-      startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
-      endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
-      'content.groupNumber': [{ required: true, message: '请选择成团默认人数', trigger: 'change' }],
-      'content.countdownTime': [
-        { required: true, message: '请选择开团倒计时结束', trigger: 'change' },
-      ],
+import { cloneDeep } from 'lodash';
+import { onMounted, reactive, ref, unref } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useFormConfig } from '@/hooks/useFormConfig';
+import { api } from '../marketing.service';
+const { t } = useI18n();
+
+// 使用表单配置hooks
+const { formLabelWidth } = useFormConfig({ enWidth: '160px', zhWidth: '130px' });
+
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps({
+  modal: {
+    type: Object,
+  },
+});
+// 添加 编辑 form
+let formRef = ref(null);
+const form = reactive({
+  model: {
+    name: '',
+    startTime: '',
+    endTime: '',
+    content: {
+      groupNumber: '',
+      countdownTime: '',
     },
-  });
-  const loading = ref(false);
+  },
+  rules: {
+    name: [{ required: true, message: t('modules.marketing.activityNameRequired'), trigger: 'blur' }],
+    startTime: [{ required: true, message: t('modules.marketing.startTimeRequired'), trigger: 'change' }],
+    endTime: [{ required: true, message: t('modules.marketing.endTimeRequired'), trigger: 'change' }],
+    'content.groupNumber': [{ required: true, message: t('modules.marketing.groupSizeRequired'), trigger: 'change' }],
+    'content.countdownTime': [
+      { required: true, message: t('modules.marketing.countdownTimeRequired'), trigger: 'change' },
+    ],
+  },
+});
+const loading = ref(false);
 
-  // 获取详情
-  async function getDetail(id) {
-    loading.value = true;
-    const { code, data } = await api.group.detail(id);
-    if (code === '200') {
-      // 处理 content 字段,如果是字符串则解析为对象
-      let content = data.content;
-      if (typeof content === 'string') {
-        try {
-          content = JSON.parse(content);
-        } catch (e) {
-          content = { groupNumber: '', countdownTime: '' };
-        }
+// 获取详情
+async function getDetail(id) {
+  loading.value = true;
+  const { code, data } = await api.group.detail(id);
+  if (code === '200') {
+    // 处理 content 字段,如果是字符串则解析为对象
+    let content = data.content;
+    if (typeof content === 'string') {
+      try {
+        content = JSON.parse(content);
+      } catch (e) {
+        content = { groupNumber: '', countdownTime: '' };
       }
-
-      form.model = {
-        ...data,
-        content: content || { groupNumber: '', countdownTime: '' },
-      };
     }
-    loading.value = false;
+
+    form.model = {
+      ...data,
+      content: content || { groupNumber: '', countdownTime: '' },
+    };
   }
+  loading.value = false;
+}
 
-  // 表单关闭时提交
-  async function confirm() {
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-      let submitForm = cloneDeep(form.model);
+// 表单关闭时提交
+async function confirm() {
+  unref(formRef).validate(async (valid) => {
+    if (!valid) return;
+    let submitForm = cloneDeep(form.model);
 
-      // 将 content 对象转换为 JSON 字符串
-      // if (submitForm.content && typeof submitForm.content === 'object') {
-      //   submitForm.content = JSON.stringify(submitForm.content);
-      // }
+    // 将 content 对象转换为 JSON 字符串
+    // if (submitForm.content && typeof submitForm.content === 'object') {
+    //   submitForm.content = JSON.stringify(submitForm.content);
+    // }
 
-      const { code } =
-        props.modal.params.type == 'add'
-          ? await api.group.add(submitForm)
-          : await api.group.edit(submitForm);
-      if (code == '200') {
-        emit('modalCallBack', { event: 'confirm' });
-      }
-    });
-  }
-  async function init() {
-    if (props.modal.params.id) {
-      await getDetail(props.modal.params.id);
+    const { code } =
+      props.modal.params.type == 'add'
+        ? await api.group.add(submitForm)
+        : await api.group.edit(submitForm);
+    if (code == '200') {
+      emit('modalCallBack', { event: 'confirm' });
     }
-  }
-  onMounted(() => {
-    init();
   });
+}
+async function init() {
+  if (props.modal.params.id) {
+    await getDetail(props.modal.params.id);
+  }
+}
+onMounted(() => {
+  init();
+});
 </script>
 
 <style lang="scss" scoped>
-  .form-tip {
-    margin-top: 8px;
-    font-size: 12px;
-    color: #909399;
-    line-height: 1.4;
-  }
+.form-tip {
+  margin-top: 8px;
+  font-size: 12px;
+  color: #909399;
+  line-height: 1.4;
+}
 </style>

+ 368 - 363
src/app/shop/admin/marketing/group/index.vue

@@ -5,115 +5,87 @@
         <el-header class="sa-header">
           <!-- 简化搜索组件 -->
           <div class="search-container">
-            <sa-search-simple
-              :searchFields="searchFields"
-              :defaultValues="defaultSearchValues"
-              @search="handleSearch"
-              @reset="handleReset"
-            >
+            <sa-search-simple :searchFields="searchFields" :defaultValues="defaultSearchValues" @search="handleSearch"
+              @reset="handleReset">
             </sa-search-simple>
           </div>
           <el-tabs class="sa-tabs" v-model="currentStatus" @tab-change="handleTabChange">
-            <el-tab-pane label="全部" name=""></el-tab-pane>
-            <el-tab-pane label="待开始" name="0"></el-tab-pane>
-            <el-tab-pane label="进行中" name="1"></el-tab-pane>
-            <el-tab-pane label="已结束" name="2"></el-tab-pane>
+            <el-tab-pane :label="t('common.all')" name=""></el-tab-pane>
+            <el-tab-pane :label="t('modules.marketing.pending')" name="0"></el-tab-pane>
+            <el-tab-pane :label="t('modules.marketing.ongoing')" name="1"></el-tab-pane>
+            <el-tab-pane :label="t('modules.marketing.ended')" name="2"></el-tab-pane>
           </el-tabs>
           <div class="sa-title sa-flex sa-row-between">
             <div class="label sa-flex">
-              <span class="left">拼团活动列表</span>
+              <span class="left">{{ t('modules.marketing.groupActivityList') }}</span>
             </div>
             <div>
-              <el-button
-                class="sa-button-refresh"
-                icon="RefreshRight"
-                @click="getData()"
-              ></el-button>
-              <el-button icon="Plus" type="primary" @click="addRow">添加活动</el-button>
+              <el-button class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
+              <el-button icon="Plus" type="primary" @click="addRow">{{ t('modules.marketing.addActivity') }}</el-button>
             </div>
           </div>
         </el-header>
         <el-main class="sa-p-0">
           <div class="sa-table-wrap" v-loading="loading">
-            <el-table
-              height="100%"
-              class="sa-table"
-              :data="table.data"
-              @selection-change="changeSelection"
-              @sort-change="fieldFilter"
-              row-key="id"
-              stripe
-            >
+            <el-table height="100%" class="sa-table" :data="table.data" @selection-change="changeSelection"
+              @sort-change="fieldFilter" row-key="id" stripe>
               <template #empty>
                 <sa-empty></sa-empty>
               </template>
               <el-table-column type="selection" align="center"></el-table-column>
-              <el-table-column
-                sortable="custom"
-                prop="id"
-                label="活动编号"
-                align="center"
-              ></el-table-column>
-              <el-table-column label="活动标题" prop="name" width="150px" />
-              <el-table-column label="活动状态" align="center">
+              <el-table-column sortable="custom" prop="id" :label="t('modules.marketing.activityId')"
+                align="center"></el-table-column>
+              <el-table-column :label="t('modules.marketing.activityTitle')" prop="name" width="150px" />
+              <el-table-column :label="t('modules.marketing.activityStatus')" align="center">
                 <template #default="scope">
                   <el-tag :type="getStatusType(scope.row.activeState)">
                     {{ getStatusText(scope.row.activeState) }}
                   </el-tag>
                 </template>
               </el-table-column>
-              <el-table-column label="拼团配置" align="center" width="220">
+              <el-table-column :label="t('modules.marketing.groupConfig')" align="center" width="280">
                 <template #default="scope">
                   <div class="group-config">
                     <div class="config-item">
-                      <span class="config-label">成团默认人数:</span>
-                      <span class="config-value">{{ getGroupNumber(scope.row.content) }}人团</span>
+                      <span class="config-label">{{ t('modules.marketing.defaultGroupSize') }}:</span>
+                      <span class="config-value">{{ getGroupNumber(scope.row.content) }}{{
+                        t('modules.marketing.personGroup') }}</span>
                     </div>
                     <div class="config-item mt-6px">
-                      <span class="config-label">开团倒计时结束:</span>
-                      <span class="config-value"
-                        >{{ getCountdownTime(scope.row.content) }}小时</span
-                      >
+                      <span class="config-label">{{ t('modules.marketing.countdownEnd') }}:</span>
+                      <span class="config-value">{{ getCountdownTime(scope.row.content) }}{{
+                        t('modules.marketing.hours') }}</span>
                     </div>
                   </div>
                 </template>
               </el-table-column>
-              <el-table-column
-                sortable="custom"
-                prop="startTime"
-                label="活动开始时间"
-                align="center"
-              >
+              <el-table-column sortable="custom" prop="startTime" :label="t('modules.marketing.startTime')"
+                align="center">
                 <template #default="scope">
                   {{ scope.row.startTime || '--' }}
                 </template>
               </el-table-column>
-              <el-table-column sortable="custom" prop="endTime" label="活动结束时间" align="center">
+              <el-table-column sortable="custom" prop="endTime" :label="t('modules.marketing.endTime')" align="center">
                 <template #default="scope">
                   {{ scope.row.endTime || '--' }}
                 </template>
               </el-table-column>
-              <el-table-column label="创建时间" align="center">
+              <el-table-column :label="t('common.createTime')" align="center">
                 <template #default="scope">
                   {{ scope.row.createTime || '--' }}
                 </template>
               </el-table-column>
-              <el-table-column label="操作" fixed="right">
+              <el-table-column :label="t('common.actions')" fixed="right">
                 <template #default="scope">
                   <div class="sa-flex">
-                    <el-button link type="primary" @click="editRow(scope.row)">编辑</el-button>
-                    <el-button link class="sa-m-l-12" type="success" @click="setGoods(scope.row)"
-                      >设置商品</el-button
-                    >
-                    <el-popconfirm
-                      width="fit-content"
-                      confirm-button-text="确认"
-                      cancel-button-text="取消"
-                      title="确认删除这条记录?"
-                      @confirm="deleteRow(scope.row.id)"
-                    >
+                    <el-button link type="primary" @click="editRow(scope.row)">{{ t('common.edit') }}</el-button>
+                    <el-button link class="sa-m-l-12" type="success" @click="setGoods(scope.row)">{{
+                      t('modules.marketing.setGoods') }}</el-button>
+                    <el-popconfirm width="fit-content" :confirm-button-text="t('common.confirm')"
+                      :cancel-button-text="t('common.cancel')" :title="t('common.confirmDelete')"
+                      @confirm="deleteRow(scope.row.id)">
                       <template #reference>
-                        <el-button link class="sa-m-l-12" type="danger"> 删除 </el-button>
+                        <el-button link class="sa-m-l-12" type="danger">{{ t('common.delete') }}</el-button>
                       </template>
                     </el-popconfirm>
                   </div>
@@ -127,11 +99,8 @@
     <el-footer class="group-index-footer">
       <sa-view-bar>
         <template #left>
-          <sa-batch-handle
-            :batchHandleTools="batchHandleTools"
-            :selectedLeng="table.selected.length"
-            @batchHandle="batchHandle"
-          ></sa-batch-handle>
+          <sa-batch-handle :batchHandleTools="batchHandleTools" :selectedLeng="table.selected.length"
+            @batchHandle="batchHandle"></sa-batch-handle>
         </template>
         <template #right>
           <sa-pagination :pageData="pageData" @updateFn="getData" />
@@ -141,348 +110,384 @@
   </el-container>
 </template>
 <script>
-  export default {
-    name: 'shop.admin.marketing.group',
-  };
+export default {
+  name: 'shop.admin.marketing.group',
+};
 </script>
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { ElMessage, ElMessageBox } from 'element-plus';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import { api } from '../marketing.service';
-  import groupEdit from './edit.vue';
-  import GoodsSelect from '@/app/shop/admin/goods/goods/select.vue';
-
-  const { pageData } = usePagination();
-
-  // 当前状态Tab
-  const currentStatus = ref('');
-
-  // 状态配置
-  const statusConfig = {
-    0: { text: '待开始', type: 'warning' },
-    1: { text: '进行中', type: 'success' },
-    2: { text: '已结束', type: 'info' },
-  };
-
-  // 获取状态显示
-  const getStatusType = (status) => statusConfig[status]?.type || 'info';
-  const getStatusText = (status) => statusConfig[status]?.text || '未知';
-
-  // 安全解析 content 字段
-  const parseContent = (content) => {
-    if (!content || content === '') {
-      return {};
-    }
-    try {
-      return JSON.parse(content);
-    } catch (e) {
-      console.warn('Failed to parse content:', content, e);
-      return {};
+import { onMounted, reactive, ref, computed } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import { useModal, usePagination } from '@/sheep/hooks';
+import { api } from '../marketing.service';
+import groupEdit from './edit.vue';
+import GoodsSelect from '@/app/shop/admin/goods/goods/select.vue';
+
+const { t } = useI18n();
+const { pageData } = usePagination();
+
+// 当前状态Tab
+const currentStatus = ref('');
+
+// 状态配置
+const statusConfig = {
+  0: { text: () => t('modules.marketing.pending'), type: 'warning' },
+  1: { text: () => t('modules.marketing.ongoing'), type: 'success' },
+  2: { text: () => t('modules.marketing.ended'), type: 'info' },
+};
+
+// 获取状态显示
+const getStatusType = (status) => statusConfig[status]?.type || 'info';
+const getStatusText = (status) => statusConfig[status]?.text() || t('common.unknown');
+
+// 安全解析 content 字段
+const parseContent = (content) => {
+  if (!content || content === '') {
+    return {};
+  }
+  try {
+    return JSON.parse(content);
+  } catch (e) {
+    console.warn('Failed to parse content:', content, e);
+    return {};
+  }
+};
+
+// 获取成团人数
+const getGroupNumber = (content) => {
+  const parsed = parseContent(content);
+  return parsed?.groupNumber || '--';
+};
+
+// 获取倒计时时间
+const getCountdownTime = (content) => {
+  const parsed = parseContent(content);
+  return parsed?.countdownTime || '--';
+};
+
+// 搜索字段配置 - 使用computed确保语言切换时更新
+const searchFields = computed(() => ({
+  name: {
+    type: 'input',
+    label: t('modules.marketing.activityTitle'),
+    placeholder: t('modules.marketing.enterActivityTitle'),
+    width: 200,
+  },
+  id: {
+    type: 'input',
+    label: t('modules.marketing.activityId'),
+    placeholder: t('modules.marketing.enterActivityId'),
+    width: 200,
+  },
+}));
+
+// 默认搜索值
+const defaultSearchValues = reactive({
+  name: '',
+  id: '',
+});
+
+// 当前搜索条件
+const currentSearchParams = ref({});
+// 列表
+const table = reactive({
+  data: [],
+  order: '',
+  sort: '',
+  selected: [],
+});
+const loading = ref(true);
+
+// 获取数据
+async function getData(page, searchParams = {}) {
+  if (page) pageData.page = page;
+  loading.value = true;
+
+  try {
+    // 构建请求参数
+    const params = {
+      page: pageData.page,
+      size: pageData.size,
+      ...searchParams,
+    };
+
+    // 根据当前状态添加筛选条件
+    if (currentStatus.value) {
+      params.activeState = parseInt(currentStatus.value);
     }
-  };
-
-  // 获取成团人数
-  const getGroupNumber = (content) => {
-    const parsed = parseContent(content);
-    return parsed?.groupNumber || '--';
-  };
-
-  // 获取倒计时时间
-  const getCountdownTime = (content) => {
-    const parsed = parseContent(content);
-    return parsed?.countdownTime || '--';
-  };
-
-  // 搜索字段配置
-  const searchFields = reactive({
-    name: {
-      type: 'input',
-      label: '活动标题',
-      placeholder: '请输入活动标题',
-      width: 200,
-    },
-    id: {
-      type: 'input',
-      label: '活动编号',
-      placeholder: '请输入活动编号',
-      width: 200,
-    },
-  });
-
-  // 默认搜索值
-  const defaultSearchValues = reactive({
-    name: '',
-    id: '',
-  });
-
-  // 当前搜索条件
-  const currentSearchParams = ref({});
-  // 列表
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-  const loading = ref(true);
-
-  // 获取数据
-  async function getData(page, searchParams = {}) {
-    if (page) pageData.page = page;
-    loading.value = true;
-
-    try {
-      // 构建请求参数
-      const params = {
-        page: pageData.page,
-        size: pageData.size,
-        ...searchParams,
-      };
-
-      // 根据当前状态添加筛选条件
-      if (currentStatus.value) {
-        params.activeState = parseInt(currentStatus.value);
-      }
 
-      // 使用 CRUD 的 list 方法
-      const { code, data, message } = await api.group.list(params);
+    // 使用 CRUD 的 list 方法
+    const { code, data, message } = await api.group.list(params);
 
-      if (code === '200') {
-        table.data = data.list || data || [];
-        pageData.total = data.total || 0;
-      } else {
-        ElMessage.error(message || '获取数据失败');
-        table.data = [];
-        pageData.total = 0;
-      }
-    } catch (error) {
-      console.error('获取数据失败:', error);
-      ElMessage.error('获取数据失败');
+    if (code === '200') {
+      table.data = data.list || data || [];
+      pageData.total = data.total || 0;
+    } else {
+      ElMessage.error(message || t('common.fetchDataFailed'));
       table.data = [];
       pageData.total = 0;
-    } finally {
-      loading.value = false;
     }
+  } catch (error) {
+    console.error('获取数据失败:', error);
+    ElMessage.error(t('common.fetchDataFailed'));
+    table.data = [];
+    pageData.total = 0;
+  } finally {
+    loading.value = false;
   }
-
-  // Tab切换处理
-  const handleTabChange = (tabName) => {
-    currentStatus.value = tabName;
-
-    // 构建筛选参数
-    const statusParams = tabName ? { activeState: parseInt(tabName) } : {};
-    const allParams = { ...currentSearchParams.value, ...statusParams };
-
-    getData(1, allParams);
-  };
-
-  // 搜索处理
-  const handleSearch = (searchParams) => {
-    currentSearchParams.value = searchParams;
-
-    // 构建筛选参数
-    const statusParams = currentStatus.value ? { activeState: parseInt(currentStatus.value) } : {};
-    const allParams = { ...searchParams, ...statusParams };
-
-    getData(1, allParams);
-  };
-
-  // 重置搜索
-  const handleReset = () => {
-    currentSearchParams.value = {};
-
-    // 构建筛选参数
-    const statusParams = currentStatus.value ? { activeState: parseInt(currentStatus.value) } : {};
-
-    getData(1, statusParams);
-  };
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
-  //table批量选择
-  function changeSelection(row) {
-    table.selected = row;
-  }
-  // 分页/批量操作
-  const batchHandleTools = [
-    // {
-    //   type: 'delete',
-    //   label: '删除',
-    //   auth: 'shop.admin.marketing.group.delete',
-    //   class: 'danger',
-    // },
-  ];
-  function addRow() {
-    useModal(
-      groupEdit,
-      { title: '新建拼团活动', type: 'add' },
-      {
-        confirm: () => {
-          getData();
-        },
+}
+
+// Tab切换处理
+const handleTabChange = (tabName) => {
+  currentStatus.value = tabName;
+
+  // 构建筛选参数
+  const statusParams = tabName ? { activeState: parseInt(tabName) } : {};
+  const allParams = { ...currentSearchParams.value, ...statusParams };
+
+  getData(1, allParams);
+};
+
+// 搜索处理
+const handleSearch = (searchParams) => {
+  currentSearchParams.value = searchParams;
+
+  // 构建筛选参数
+  const statusParams = currentStatus.value ? { activeState: parseInt(currentStatus.value) } : {};
+  const allParams = { ...searchParams, ...statusParams };
+
+  getData(1, allParams);
+};
+
+// 重置搜索
+const handleReset = () => {
+  currentSearchParams.value = {};
+
+  // 构建筛选参数
+  const statusParams = currentStatus.value ? { activeState: parseInt(currentStatus.value) } : {};
+
+  getData(1, statusParams);
+};
+// table 字段排序
+function fieldFilter({ prop, order }) {
+  table.order = order == 'ascending' ? 'asc' : 'desc';
+  table.sort = prop;
+  getData();
+}
+//table批量选择
+function changeSelection(row) {
+  table.selected = row;
+}
+// 分页/批量操作
+const batchHandleTools = [
+  // {
+  //   type: 'delete',
+  //   label: '删除',
+  //   auth: 'shop.admin.marketing.group.delete',
+  //   class: 'danger',
+  // },
+];
+function addRow() {
+  useModal(
+    groupEdit,
+    { title: t('modules.marketing.createGroupActivity'), type: 'add' },
+    {
+      confirm: () => {
+        getData();
       },
-    );
-  }
+    },
+  );
+}
+
+function editRow(row) {
+  useModal(
+    groupEdit,
+    {
+      title: t('modules.marketing.editGroupActivity'),
+      type: 'edit',
+      id: row.id,
+    },
+    {
+      confirm: () => {
+        getData();
+      },
+    },
+  );
+}
 
-  function editRow(row) {
+// 设置商品
+async function setGoods(row) {
+  const { code, data } = await api.group.getActivityProductIds(row.id);
+  if (code == '200') {
     useModal(
-      groupEdit,
+      GoodsSelect,
       {
-        title: '编辑拼团活动',
-        type: 'edit',
-        id: row.id,
+        title: '选择商品',
+        multiple: true,
+        ids: data.map((item) => String(item)) || [], // 已选择的商品ID
       },
       {
-        confirm: () => {
-          getData();
+        confirm: (res) => {
+          // 更新活动的商品列表
+          updateActivityGoods(row.id, res.data);
         },
       },
     );
   }
+}
 
-  // 设置商品
-  async function setGoods(row) {
-    const { code, data } = await api.group.getActivityProductIds(row.id);
-    if (code == '200') {
-      useModal(
-        GoodsSelect,
-        {
-          title: '选择商品',
-          multiple: true,
-          ids: data.map((item) => String(item)) || [], // 已选择的商品ID
-        },
-        {
-          confirm: (res) => {
-            // 更新活动的商品列表
-            updateActivityGoods(row.id, res.data);
-          },
-        },
-      );
-    }
-  }
-
-  // 更新活动商品
-  async function updateActivityGoods(activityId, goodsList) {
-    try {
-      // 使用 CRUD 的 edit 方法更新活动商品
-      const productIds = goodsList.map((item) => ({ productId: item.id }));
+// 更新活动商品
+async function updateActivityGoods(activityId, goodsList) {
+  try {
+    // 使用 CRUD 的 edit 方法更新活动商品
+    const productIds = goodsList.map((item) => ({ productId: item.id }));
 
-      const { code, message } = await api.group.addActivityProduct(activityId, {
-        list: productIds,
-      });
+    const { code, message } = await api.group.addActivityProduct(activityId, {
+      list: productIds,
+    });
 
-      if (code === '200') {
-        ElMessage.success('商品设置成功');
-        getData(); // 刷新列表
-      }
-    } catch (error) {
-      console.error('设置商品失败:', error);
+    if (code === '200') {
+      ElMessage.success('商品设置成功');
+      getData(); // 刷新列表
     }
+  } catch (error) {
+    console.error('设置商品失败:', error);
   }
-
-  // 删除单条记录
-  async function deleteRow(id) {
-    console.log(api.group);
-    try {
-      const { code, message } = await api.group.delete({ id });
-      if (code === '200') {
-        ElMessage.success('删除成功');
-        getData();
-      } else {
-        ElMessage.error(message || '删除失败');
-      }
-    } catch (error) {
-      console.error('删除失败:', error);
-      ElMessage.error('删除失败');
+}
+
+// 删除单条记录
+async function deleteRow(id) {
+  console.log(api.group);
+  try {
+    const { code, message } = await api.group.delete({ id });
+    if (code === '200') {
+      ElMessage.success('删除成功');
+      getData();
+    } else {
+      ElMessage.error(message || '删除失败');
     }
+  } catch (error) {
+    console.error('删除失败:', error);
+    ElMessage.error('删除失败');
   }
-  async function batchHandle(type) {
-    let ids = [];
-    table.selected.forEach((row) => {
-      ids.push(row.id);
-    });
-    switch (type) {
-      case 'delete':
-        ElMessageBox.confirm('此操作将删除, 是否继续?', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning',
-        }).then(() => {
-          batchDeleteRows(ids);
-        });
-        break;
-      default:
-        // 其他批量操作可以在这里添加
-        ElMessage.info('该功能暂未实现');
-    }
+}
+async function batchHandle(type) {
+  let ids = [];
+  table.selected.forEach((row) => {
+    ids.push(row.id);
+  });
+  switch (type) {
+    case 'delete':
+      ElMessageBox.confirm('此操作将删除, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }).then(() => {
+        batchDeleteRows(ids);
+      });
+      break;
+    default:
+      // 其他批量操作可以在这里添加
+      ElMessage.info('该功能暂未实现');
   }
-
-  // 批量删除方法
-  async function batchDeleteRows(ids) {
-    try {
-      const { code, message } = await api.group.batchDelete(ids);
-      if (code === '200') {
-        ElMessage.success('批量删除成功');
-        getData();
-        table.selected = []; // 清空选中项
-      }
-    } catch (error) {
-      console.error('批量删除失败:', error);
-      ElMessage.error('批量删除失败');
+}
+
+// 批量删除方法
+async function batchDeleteRows(ids) {
+  try {
+    const { code, message } = await api.group.batchDelete(ids);
+    if (code === '200') {
+      ElMessage.success('批量删除成功');
+      getData();
+      table.selected = []; // 清空选中项
     }
+  } catch (error) {
+    console.error('批量删除失败:', error);
+    ElMessage.error('批量删除失败');
   }
+}
 
-  onMounted(() => {
-    getData();
-  });
+onMounted(() => {
+  getData();
+});
 </script>
 <style lang="scss" scoped>
-  .group-view {
+.group-view {
+  height: 100%;
+
+  .group-index-main {
     height: 100%;
+  }
+
+  .group-index-footer {
+    height: auto;
+  }
+
+  .el-header {
+    height: auto;
+  }
 
-    .group-index-main {
+  .el-main {
+    .sa-table-wrap {
       height: 100%;
     }
+  }
 
-    .group-index-footer {
-      height: auto;
-    }
+  .search-container {
+    margin-bottom: 16px;
+  }
 
-    .el-header {
-      height: auto;
-    }
+  .sa-tabs {
+    margin-bottom: 16px;
+  }
 
-    .el-main {
-      .sa-table-wrap {
-        height: 100%;
+  .sa-table-line-1 {
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 1;
+    line-clamp: 1;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+  }
+
+  .activity-info {
+    display: flex;
+    align-items: center;
+  }
+
+  .group-config {
+    .config-item {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      white-space: nowrap;
+      margin-bottom: 4px;
+
+      .config-label {
+        flex-shrink: 0;
+        margin-right: 4px;
       }
-    }
 
-    .search-container {
-      margin-bottom: 16px;
     }
 
-    .sa-tabs {
-      margin-bottom: 16px;
+    .config-item:last-child {
+      margin-bottom: 0;
     }
+  }
 
-    .sa-table-line-1 {
-      display: -webkit-box;
-      -webkit-box-orient: vertical;
-      -webkit-line-clamp: 1;
-      line-clamp: 1;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      word-break: break-all;
-    }
+  // 英文环境下的特殊样式
+  [data-locale="en-US"] .group-config {
+    .config-item {
+      .config-label {
+        font-size: 11px;
+        margin-right: 2px;
+      }
 
-    .activity-info {
-      display: flex;
-      align-items: center;
+      .config-value {
+        font-size: 11px;
+      }
     }
   }
+}
 </style>

+ 11 - 0
src/app/shop/admin/order/order.service.js

@@ -138,6 +138,17 @@ const api = {
         method: 'POST',
         data,
       }),
+    // 导入发货单
+    importDelivery: (data) =>
+      request({
+        url: 'mall/order/importDelivery',
+        headers: {
+          'Content-Type': 'multipart/form-data',
+        },
+        method: 'POST',
+        data,
+        responseType: 'blob', // 支持文件流下载
+      }),
     customDispatch: (data) =>
       request({
         url: '/shop/admin/order/order/customDispatch',

+ 143 - 152
src/app/shop/admin/order/order/batchDispatch.vue

@@ -2,58 +2,22 @@
   <el-container>
     <el-main>
       <div class="batch-dispatch sa-m-b-20">
-        <div class="title"> 方法一:如使用导入订单,需完善发货表单物流信息后再上传 </div>
+        <div class="title">导入发货单,系统将自动处理发货信息</div>
         <div class="order-content sa-flex sa-flex-col">
           <div class="sa-flex sa-flex-col">
             <div class="order-file sa-table-line-1 sa-m-b-8">
               <span v-if="state.file">{{ state.file.name }}</span>
             </div>
-            <el-upload
-              action
-              :http-request="onImportFile"
-              :show-file-list="false"
-              accept=".xlsx,.xls"
-            >
+            <el-upload action :http-request="onImportFile" :show-file-list="false" accept=".xlsx,.xls">
               <el-button v-if="!state.file">导入发货单</el-button>
               <el-button class="is-link" type="primary" v-if="state.file">重新选择文件</el-button>
             </el-upload>
           </div>
-          <el-form class="sa-m-t-22" ref="expressRef" :inline="true" :model="express">
-            <el-form-item
-              label="快递公司"
-              prop="code"
-              :rules="[{ required: true, message: '请选择快递公司', trigger: 'none' }]"
-            >
-              <el-select
-                v-model="express.code"
-                placeholder="请选择快递公司"
-                @change="onChangeExpressCode"
-                filterable
-                remote
-                reserve-keyword
-                :remote-method="remoteMethod"
-                :loading="deliverCompany.loading"
-              >
-                <el-option
-                  v-for="dc in deliverCompany.data"
-                  :key="dc"
-                  :label="dc.name"
-                  :value="dc.code"
-                  :ref="`dc-${dc.code}`"
-                  :data-name="dc.name"
-                  >{{ dc.name }}&nbsp;({{ dc.code }})</el-option
-                >
-                <div class="sa-p-l-15 sa-p-r-27">
-                  <sa-pagination
-                    layout="total, prev, pager, next"
-                    :pageData="deliverCompany.pageData"
-                    @updateFn="getDeliverCompany"
-                  />
-                </div>
-              </el-select>
-              <div class="select-append" @click="onDispatch">发货</div>
-            </el-form-item>
-          </el-form>
+          <div class="sa-m-t-22" v-if="state.file">
+            <el-button type="primary" @click="onDispatch" :loading="state.loading" :disabled="!state.file">
+              批量发货
+            </el-button>
+          </div>
         </div>
       </div>
       <!-- <div class="batch-dispatch">
@@ -75,131 +39,158 @@
   </el-container>
 </template>
 <script setup>
-  import { getCurrentInstance, reactive } from 'vue';
-  import { useModal } from '@/sheep/hooks';
-  import { api } from '../order.service';
-  import useExpress from '@/app/shop/admin/data/express/express.js';
-  import OrderLoopDispatch from './loopDispatch.vue';
+import { getCurrentInstance, reactive } from 'vue';
+import { ElMessage } from 'element-plus';
+import { api } from '../order.service';
+const props = defineProps(['modal']);
+const { proxy } = getCurrentInstance();
 
-  const props = defineProps(['modal']);
+const state = reactive({
+  file: '',
+  loading: false,
+});
 
-  const { proxy } = getCurrentInstance();
+// 导入发货单
+function onImportFile(file) {
+  state.file = file.file;
+}
 
-  const state = reactive({
-    file: '',
-  });
+// 批量发货
+async function onDispatch() {
+  if (!state.file) {
+    ElMessage.warning('请先选择发货单文件');
+    return;
+  }
 
-  const { express, deliverCompany, getDeliverCompany, onChangeExpressCode, remoteMethod } =
-    useExpress();
+  state.loading = true;
 
-  // 导入发货单
-  function onImportFile(file) {
-    state.file = file.file;
-  }
+  try {
+    const formData = new FormData();
+    formData.append('file', state.file);
 
-  // 发货单发货 TODO
-  async function onDispatch() {
-    proxy.$refs.expressRef &&
-      proxy.$refs.expressRef.validate(async (valid) => {
-        if (valid) {
-          const dispatchData = {
-            action: 'multiple',
-            'express[name]': express.name,
-            'express[code]': express.code,
-            file: state.file,
-          };
-
-          let dispatchForm = new FormData();
-          for (let name in dispatchData) {
-            dispatchForm.append(name, dispatchData[name]);
-          }
+    const response = await api.order.importDelivery(formData);
+
+    // 检查响应类型
+    const contentType = response.headers['content-type'];
+
+    if (contentType && contentType.includes('application/json')) {
+      // JSON响应,表示成功 - 需要将blob转换为文本再解析
+      const text = await response.data.text();
+      const result = JSON.parse(text);
+      if (result.code == 200) {
+        ElMessage.success('批量发货成功');
+        // 关闭弹窗并触发刷新
+        proxy.$emit('modalCallBack', { action: 'confirm', refresh: true });
+      } else {
+        ElMessage.error('批量发货失败');
+      }
+    } else {
+      // 文件流响应,表示有失败的记录,需要下载错误文件
+      const blob = response.data;
+
+      // 从响应头获取文件名
+      const now = new Date();
+      const dateTimeStr = now.getFullYear() +
+        String(now.getMonth() + 1).padStart(2, '0') +
+        String(now.getDate()).padStart(2, '0') + '_' +
+        String(now.getHours()).padStart(2, '0') +
+        String(now.getMinutes()).padStart(2, '0') +
+        String(now.getSeconds()).padStart(2, '0');
+      let filename = `批量发货失败记录_${dateTimeStr}.xlsx`; // 默认文件名
+      const contentDisposition = response.headers['content-disposition'];
 
-          let { code, data } = await api.order.dispatchByUpload(dispatchForm);
-          if (code == '200')
-            useModal(
-              OrderLoopDispatch,
-              {
-                title: '批量操作',
-                method: 'upload',
-                data: data,
-              },
-              {
-                confirm: () => {},
-              },
-            );
+      if (contentDisposition) {
+        // 尝试从 content-disposition 中提取文件名
+        const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
+        if (filenameMatch && filenameMatch[1]) {
+          filename = filenameMatch[1].replace(/['"]/g, ''); // 移除引号
+          // 如果文件名是 URL 编码的,进行解码
+          try {
+            filename = decodeURIComponent(filename);
+          } catch (e) {
+            // 解码失败,使用原文件名
+            console.warn('文件名解码失败,使用原文件名:', filename);
+          }
         }
-      });
-  }
+      }
+
+      // 创建下载链接
+      const url = window.URL.createObjectURL(blob);
+      const link = document.createElement('a');
+      link.href = url;
+      link.download = filename;
+      document.body.appendChild(link);
+      link.click();
+      document.body.removeChild(link);
+      window.URL.revokeObjectURL(url);
 
-  // 批量发货
-  async function onBatchDispatch() {
-    const { code, data } = await api.order.dispatch({
-      order_ids: props.modal.params.order_ids,
-      action: 'multiple',
-    });
-    if (code == '200')
-      useModal(
-        OrderLoopDispatch,
-        {
-          title: '批量操作',
-          method: 'api',
-          data: data,
-        },
-        {
-          confirm: () => {},
-        },
-      );
+      ElMessage.warning('部分发货失败,已下载失败记录');
+    }
+  } catch (error) {
+    console.error('批量发货失败:', error);
+    ElMessage.error('批量发货失败');
+  } finally {
+    state.loading = false;
   }
+}
+
+
 </script>
 <style lang="scss" scoped>
-  .batch-dispatch {
-    background: var(--sa-table-header-bg);
-    border-radius: 4px;
-    padding: 0 16px 24px;
-    .title {
-      line-height: 18px;
-      margin-bottom: 20px;
-      padding: 15px 0;
-      display: flex;
-      align-items: center;
-      .el-button {
-        height: 18px;
-      }
+.batch-dispatch {
+  background: var(--sa-table-header-bg);
+  border-radius: 4px;
+  padding: 0 16px 24px;
+
+  .title {
+    line-height: 18px;
+    margin-bottom: 20px;
+    padding: 15px 0;
+    display: flex;
+    align-items: center;
+
+    .el-button {
+      height: 18px;
     }
-    .order-content {
-      .order-file {
-        height: 16px;
-        line-height: 16px;
-        font-size: 12px;
-        font-weight: 400;
-        color: var(--sa-font);
-      }
-      .el-form--inline .el-form-item {
-        margin-right: 0;
-        margin-bottom: 0;
-      }
+  }
 
-      :deep() {
-        .el-select {
-          flex: 1;
-        }
-        .el-input__inner {
-          border-radius: 4px 0 0 4px;
-        }
+  .order-content {
+    .order-file {
+      height: 16px;
+      line-height: 16px;
+      font-size: 12px;
+      font-weight: 400;
+      color: var(--sa-font);
+    }
+
+    .el-form--inline .el-form-item {
+      margin-right: 0;
+      margin-bottom: 0;
+    }
+
+    :deep() {
+      .el-select {
+        flex: 1;
       }
-      .select-append {
-        padding: 0 16px;
-        line-height: 30px;
-        background: var(--sa-table-header-bg);
-        border: 1px solid var(--sa-border);
-        box-sizing: border-box;
-        border-left: none;
-        border-radius: 0 4px 4px 0;
-        font-size: 12px;
-        font-weight: 400;
-        color: var(--sa-font);
-        cursor: pointer;
+
+      .el-input__inner {
+        border-radius: 4px 0 0 4px;
       }
     }
+
+    .select-append {
+      padding: 0 16px;
+      line-height: 30px;
+      background: var(--sa-table-header-bg);
+      border: 1px solid var(--sa-border);
+      box-sizing: border-box;
+      border-left: none;
+      border-radius: 0 4px 4px 0;
+      font-size: 12px;
+      font-weight: 400;
+      color: var(--sa-font);
+      cursor: pointer;
+    }
   }
+}
 </style>

+ 436 - 453
src/app/shop/admin/order/order/index.vue

@@ -5,74 +5,44 @@
         <el-header class="sa-header">
           <!-- 简化搜索组件 -->
           <div class="search-container">
-            <sa-search-simple
-              :searchFields="searchFields"
-              :defaultValues="defaultSearchValues"
-              @search="handleSearch"
-              @reset="handleReset"
-            >
+            <sa-search-simple :searchFields="searchFields" :defaultValues="defaultSearchValues" @search="handleSearch"
+              @reset="handleReset">
               <template #custom="{ data }">
-                <el-form-item :label="t('modules.order.orderTime')">
-                  <el-date-picker
-                    v-model="data.createTime"
-                    type="datetimerange"
-                    value-format="YYYY-MM-DD HH:mm:ss"
-                    format="YYYY-MM-DD HH:mm:ss"
-                    :range-separator="t('common.to')"
-                    :start-placeholder="t('common.startTime')"
-                    :end-placeholder="t('common.endTime')"
-                    :editable="false"
-                    style="width: 350px"
-                  />
+                <el-form-item label="下单时间">
+                  <el-date-picker v-model="data.createTime" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss"
+                    format="YYYY-MM-DD HH:mm:ss" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间"
+                    :editable="false" style="width: 350px" />
                 </el-form-item>
               </template>
             </sa-search-simple>
           </div>
           <el-tabs class="sa-tabs" v-model="currentStatus" @tab-change="handleTabChange">
-            <el-tab-pane :label="t('common.all')" name="all"></el-tab-pane>
-            <el-tab-pane :label="t('modules.order.pendingPayment')" name="to_pay"></el-tab-pane>
-            <el-tab-pane :label="t('modules.order.paid')" name="paid"></el-tab-pane>
-            <el-tab-pane :label="t('modules.order.refunded')" name="refund"></el-tab-pane>
-            <el-tab-pane :label="t('modules.order.pendingDelivery')" name="to_ship"></el-tab-pane>
-            <el-tab-pane :label="t('modules.order.completed')" name="completed"></el-tab-pane>
-            <el-tab-pane :label="t('modules.order.pendingReceive')" name="to_receive"></el-tab-pane>
-            <el-tab-pane :label="t('modules.order.closed')" name="to_closed"></el-tab-pane>
-            <el-tab-pane :label="t('modules.order.cancelled')" name="cancellation"></el-tab-pane>
+            <el-tab-pane label="全部" name="all"></el-tab-pane>
+            <el-tab-pane label="待付款" name="to_pay"></el-tab-pane>
+            <el-tab-pane label="已支付" name="paid"></el-tab-pane>
+            <el-tab-pane label="已退款" name="refund"></el-tab-pane>
+            <el-tab-pane label="待发货" name="to_ship"></el-tab-pane>
+            <el-tab-pane label="待收货" name="to_receive"></el-tab-pane>
+            <el-tab-pane label="已完成" name="completed"></el-tab-pane>
+            <el-tab-pane label="已关闭" name="to_closed"></el-tab-pane>
+            <el-tab-pane label="已取消" name="cancellation"></el-tab-pane>
           </el-tabs>
           <div class="sa-title sa-flex sa-row-between">
             <div class="label sa-flex">
-              <span class="left">{{ t('modules.order.orderList') }}</span>
+              <span class="left">订单列表</span>
             </div>
             <div>
-              <el-button
-                class="sa-button-refresh"
-                icon="RefreshRight"
-                @click="getData()"
-              ></el-button>
-              <el-button :loading="exportLoading" :disabled="exportLoading" @click="onExport()"
-                >订单导出</el-button
-              >
-              <el-button
-                v-if="currentStatus == 'to_ship'"
-                :loading="exportLoading"
-                :disabled="exportLoading"
-                @click="onExportDelivery()"
-                >导出发货单</el-button
-              >
+              <el-button class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
+              <el-button :loading="exportLoading" :disabled="exportLoading" @click="onExport()">导出订单</el-button>
+              <el-button v-if="currentStatus == 'to_ship'" :loading="exportLoading" :disabled="exportLoading"
+                @click="onExportDelivery()">导出发货单</el-button>
             </div>
           </div>
         </el-header>
         <el-main class="sa-p-0">
           <div class="sa-table-wrap" v-loading="loading">
-            <el-table
-              height="100%"
-              class="sa-table"
-              :data="table.data"
-              @selection-change="changeSelection"
-              @sort-change="fieldFilter"
-              row-key="id"
-              stripe
-            >
+            <el-table height="100%" class="sa-table" :data="table.data" @selection-change="changeSelection"
+              @sort-change="fieldFilter" row-key="id" stripe>
               <template #empty>
                 <sa-empty></sa-empty>
               </template>
@@ -86,15 +56,9 @@
               </el-table-column>
               <el-table-column label="商品信息" min-width="300">
                 <template #default="scope">
-                  <div
-                    class="sa-flex"
-                    v-if="scope.row.orderInfoVO && scope.row.orderInfoVO.length > 0"
-                  >
-                    <el-image
-                      :src="scope.row.orderInfoVO[0].image"
-                      style="width: 60px; height: 60px; margin-right: 12px"
-                      fit="cover"
-                    />
+                  <div class="sa-flex" v-if="scope.row.orderInfoVO && scope.row.orderInfoVO.length > 0">
+                    <el-image :src="scope.row.orderInfoVO[0].image"
+                      style="width: 60px; height: 60px; margin-right: 12px" fit="cover" />
                     <div>
                       <div class="goods-title">{{ scope.row.orderInfoVO[0].productName }}</div>
                     </div>
@@ -147,9 +111,7 @@
               <el-table-column label="物流信息" min-width="150" align="center">
                 <template #default="scope">
                   <div v-if="scope.row.deliveryId">
-                    <div class="delivery-name"
-                      >{{ scope.row.deliveryName || '快递公司' }}:{{ scope.row.deliveryId }}</div
-                    >
+                    <div class="delivery-name">{{ scope.row.deliveryName || '快递公司' }}:{{ scope.row.deliveryId }}</div>
                   </div>
                   <div v-else>未发货</div>
                 </template>
@@ -167,25 +129,12 @@
               <el-table-column label="操作" min-width="150" fixed="right">
                 <template #default="scope">
                   <div class="sa-flex">
-                    <el-button class="is-link" type="primary" @click="onSend(scope.row)"
-                      >发货测试</el-button
-                    >
-                    <el-button
-                      v-if="scope.row.status === 5"
-                      class="is-link"
-                      type="primary"
-                      @click="onSend(scope.row)"
-                      >发货</el-button
-                    >
-                    <el-button class="is-link" type="primary" @click="detailRow(scope.row.id)"
-                      >详情</el-button
-                    >
-                    <el-button
-                      v-if="scope.row.status === 1"
-                      class="is-link sa-m-l-12"
-                      type="danger"
-                      @click="cancelOrder(scope.row.id)"
-                    >
+                    <el-button class="is-link" type="primary" @click="onSend(scope.row)">发货测试</el-button>
+                    <el-button v-if="scope.row.status === 5" class="is-link" type="primary"
+                      @click="onSend(scope.row)">发货</el-button>
+                    <el-button class="is-link" type="primary" @click="detailRow(scope.row.id)">详情</el-button>
+                    <el-button v-if="scope.row.status === 1" class="is-link sa-m-l-12" type="danger"
+                      @click="cancelOrder(scope.row.id)">
                       取消
                     </el-button>
                   </div>
@@ -199,11 +148,8 @@
     <el-footer class="order-index-footer">
       <sa-view-bar>
         <template #left>
-          <sa-batch-handle
-            :batchHandleTools="batchHandleTools"
-            :selectedLeng="table.selected.length"
-            @batchHandle="batchHandle"
-          ></sa-batch-handle>
+          <sa-batch-handle :batchHandleTools="batchHandleTools" :selectedLeng="table.selected.length"
+            @batchHandle="batchHandle"></sa-batch-handle>
         </template>
         <template #right>
           <sa-pagination :pageData="pageData" @updateFn="getData" />
@@ -213,451 +159,488 @@
   </el-container>
 </template>
 <script>
-  export default {
-    name: 'shop.admin.order.order',
-  };
+export default {
+  name: 'shop.admin.order.order',
+};
 </script>
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api, getOrderStatusInfo, getPinkStatusInfo } from '../order.service';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import { useI18n } from 'vue-i18n';
-  import OrderDispatch from './dispatch.vue';
-  import OrderBatchDispatch from './batchDispatch.vue';
-
-  const { t } = useI18n();
-  import OrderDetail from './detail.vue';
-  import UserEdit from '../../user/list/edit.vue';
-  import { ElMessage, ElMessageBox } from 'element-plus';
-
-  // 搜索字段配置
-  const searchFields = reactive({
-    orderId: {
-      type: 'input',
-      get label() {
-        return t('modules.order.orderNo');
-      },
-      get placeholder() {
-        return t('form.inputOrderNo');
-      },
-      width: 200,
-    },
-    realName: {
-      type: 'input',
-      get label() {
-        return t('modules.user.userName');
-      },
-      get placeholder() {
-        return t('form.inputUserName');
-      },
-      width: 200,
-    },
-    userPhone: {
-      type: 'input',
-      get label() {
-        return t('modules.user.userPhone');
-      },
-      get placeholder() {
-        return t('form.inputPhone');
-      },
-      width: 200,
-    },
-    deliveryId: {
-      type: 'input',
-      get label() {
-        return t('modules.order.trackingNumber');
-      },
-      get placeholder() {
-        return t('form.inputTrackingNumber');
-      },
-      width: 200,
+import { onMounted, reactive, ref, watch } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { api, getOrderStatusInfo, getPinkStatusInfo } from '../order.service';
+import { useModal, usePagination } from '@/sheep/hooks';
+import OrderDispatch from './dispatch.vue';
+import OrderBatchDispatch from './batchDispatch.vue';
+import OrderDetail from './detail.vue';
+import UserEdit from '../../user/list/edit.vue';
+
+const route = useRoute();
+const router = useRouter();
+
+// 搜索字段配置
+const searchFields = reactive({
+  orderId: {
+    type: 'input',
+    label: '订单号',
+    placeholder: '请输入订单号',
+    width: 200,
+  },
+  realName: {
+    type: 'input',
+    label: '用户姓名',
+    placeholder: '请输入用户姓名',
+    width: 200,
+  },
+  userPhone: {
+    type: 'input',
+    label: '用户手机',
+    placeholder: '请输入手机号',
+    width: 200,
+  },
+  deliveryId: {
+    type: 'input',
+    label: '快递单号',
+    placeholder: '请输入快递单号',
+    width: 200,
+  },
+});
+
+// 默认搜索值
+const defaultSearchValues = reactive({
+  orderId: '',
+  realName: '',
+  userPhone: '',
+  deliveryId: '',
+  createTime: [],
+});
+
+// 当前状态标签 - 从URL参数初始化
+const currentStatus = ref(route.query.status || 'all');
+
+// 状态映射 - 根据新的状态机定义
+const statusMap = {
+  all: {}, // 全部 - 不传订单状态参数
+  to_pay: { status: 1 }, // 待支付:订单状态为1
+  cancellation: { status: 2 }, // 订单取消:订单状态为2
+  paid: { status: 3 }, // 已支付:订单状态为3
+  refund: { status: 4 }, // 失败已退款:订单状态为4
+  to_ship: { status: 5 }, // 待发货:订单状态为5
+  to_closed: { status: 6 }, // 未中奖关闭:订单状态为6
+  to_receive: { status: 7 }, // 待收货:订单状态为7
+  completed: { status: 8 }, // 订单完成:订单状态为8
+};
+
+// 搜索处理
+const handleSearch = (searchParams) => {
+  // 合并搜索条件和当前tab状态
+  const statusParams = statusMap[currentStatus.value] || {};
+  const mergedParams = { ...searchParams, ...statusParams };
+  getData(1, mergedParams);
+};
+
+// 重置处理
+const handleReset = () => {
+  // 重置时只保留当前tab状态
+  const statusParams = statusMap[currentStatus.value] || {};
+  getData(1, statusParams);
+};
+
+// 标签切换处理
+const handleTabChange = (status) => {
+  // 立即更新tab状态,不依赖接口结果
+  currentStatus.value = status;
+
+  // 同步URL参数
+  router.replace({
+    query: {
+      ...route.query,
+      status: status === 'all' ? undefined : status, // 'all'状态不需要在URL中显示
     },
   });
 
-  // 默认搜索值
-  const defaultSearchValues = reactive({
-    orderId: '',
-    realName: '',
-    userPhone: '',
-    deliveryId: '',
-    createTime: [],
-  });
+  // 获取当前tab对应的状态参数
+  const statusParams = statusMap[status] || {};
 
-  // 当前状态标签
-  const currentStatus = ref('all');
+  // 合并当前搜索条件和状态参数
+  const mergedParams = { ...currentSearchParams.value, ...statusParams };
 
-  // 状态映射 - 根据新的状态机定义
-  const statusMap = {
-    all: {}, // 全部 - 不传订单状态参数
-    to_pay: { status: 1 }, // 待支付:订单状态为1
-    cancellation: { status: 2 }, // 订单取消:订单状态为2
-    paid: { status: 3 }, // 已支付:订单状态为3
-    refund: { status: 4 }, // 失败已退款:订单状态为4
-    to_ship: { status: 5 }, // 待发货:订单状态为5
-    to_closed: { status: 6 }, // 未中奖关闭:订单状态为6
-    to_receive: { status: 7 }, // 待收货:订单状态为7
-    completed: { status: 8 }, // 订单完成:订单状态为8
-  };
+  // 调用数据获取,无论成功与否都不影响tab状态
+  getData(1, mergedParams);
+};
 
-  // 搜索处理
-  const handleSearch = (searchParams) => {
-    // 合并搜索条件和当前tab状态
-    const statusParams = statusMap[currentStatus.value] || {};
-    const mergedParams = { ...searchParams, ...statusParams };
-    getData(1, mergedParams);
+// 通用状态处理函数
+const getStatusText = (type, value) => {
+  const statusMap = {
+    order: getOrderStatusInfo,
+    pink: getPinkStatusInfo,
   };
+  return statusMap[type]?.(value)?.text || '未知';
+};
 
-  // 重置处理
-  const handleReset = () => {
-    // 重置时只保留当前tab状态
-    const statusParams = statusMap[currentStatus.value] || {};
-    getData(1, statusParams);
+const getStatusType = (type, value) => {
+  const statusMap = {
+    order: getOrderStatusInfo,
+    pink: getPinkStatusInfo,
   };
+  return statusMap[type]?.(value)?.type || 'info';
+};
 
-  // 标签切换处理
-  const handleTabChange = (status) => {
-    // 立即更新tab状态,不依赖接口结果
-    currentStatus.value = status;
-
-    // 获取当前tab对应的状态参数
-    const statusParams = statusMap[status] || {};
-
-    // 合并当前搜索条件和状态参数
-    const mergedParams = { ...currentSearchParams.value, ...statusParams };
-
-    // 调用数据获取,无论成功与否都不影响tab状态
-    getData(1, mergedParams);
-  };
+const loading = ref(true);
 
-  // 通用状态处理函数
-  const getStatusText = (type, value) => {
-    const statusMap = {
-      order: getOrderStatusInfo,
-      pink: getPinkStatusInfo,
-    };
-    return statusMap[type]?.(value)?.text || '未知';
-  };
+// 表格
+const table = reactive({
+  data: [],
+  order: 'desc',
+  sort: 'id',
+  selected: [],
+});
 
-  const getStatusType = (type, value) => {
-    const statusMap = {
-      order: getOrderStatusInfo,
-      pink: getPinkStatusInfo,
-    };
-    return statusMap[type]?.(value)?.type || 'info';
-  };
+const { pageData } = usePagination();
 
-  const loading = ref(true);
+// 保存当前搜索条件(不包含状态参数)
+const currentSearchParams = ref({});
 
-  // 表格
-  const table = reactive({
-    data: [],
-    order: 'desc',
-    sort: 'id',
-    selected: [],
-  });
+// 获取数据
+async function getData(page, searchParams = {}) {
+  // 分离搜索条件和状态参数
+  const { paid, status, refundStatus, ...pureSearchParams } = searchParams;
 
-  const { pageData } = usePagination();
+  // 保存纯搜索条件(不包含状态参数)
+  if (Object.keys(pureSearchParams).length > 0) {
+    currentSearchParams.value = { ...pureSearchParams };
+  }
 
-  // 保存当前搜索条件(不包含状态参数)
-  const currentSearchParams = ref({});
+  if (page) pageData.page = page;
+  loading.value = true;
 
-  // 获取数据
-  async function getData(page, searchParams = {}) {
-    // 分离搜索条件和状态参数
-    const { paid, status, refundStatus, ...pureSearchParams } = searchParams;
+  try {
+    // 构建请求参数
+    const requestData = {
+      page: pageData.page,
+      size: pageData.size,
+      ...searchParams,
+    };
 
-    // 保存纯搜索条件(不包含状态参数)
-    if (Object.keys(pureSearchParams).length > 0) {
-      currentSearchParams.value = { ...pureSearchParams };
+    // 处理时间范围搜索
+    if (searchParams.createTime && searchParams.createTime.length === 2) {
+      requestData.startTime = searchParams.createTime[0];
+      requestData.endTime = searchParams.createTime[1];
+      delete requestData.createTime;
     }
 
-    if (page) pageData.page = page;
-    loading.value = true;
-
-    try {
-      // 构建请求参数
-      const requestData = {
-        page: pageData.page,
-        size: pageData.size,
-        ...searchParams,
-      };
-
-      // 处理时间范围搜索
-      if (searchParams.createTime && searchParams.createTime.length === 2) {
-        requestData.startTime = searchParams.createTime[0];
-        requestData.endTime = searchParams.createTime[1];
-        delete requestData.createTime;
-      }
-
-      const { code, data } = await api.order.list(requestData);
+    const { code, data } = await api.order.list(requestData);
 
-      if (code == '200') {
-        table.data = data.list;
-        pageData.page = data.pageNum;
-        pageData.total = data.total;
-      }
-    } catch (error) {
-      console.error('获取订单列表失败:', error);
-      // 接口失败时也要确保loading状态正确
-    } finally {
-      loading.value = false;
+    if (code == '200') {
+      table.data = data.list;
+      pageData.page = data.pageNum;
+      pageData.total = data.total;
     }
+  } catch (error) {
+    console.error('获取订单列表失败:', error);
+    // 接口失败时也要确保loading状态正确
+  } finally {
+    loading.value = false;
   }
+}
+
+// table 字段排序
+function fieldFilter({ prop, order }) {
+  table.order = order == 'ascending' ? 'asc' : 'desc';
+  table.sort = prop;
+  getData();
+}
+
+// table 批量选择
+function changeSelection(row) {
+  table.selected = row;
+}
+
+// 导出订单/导出发货单
+const exportLoading = ref(false);
+async function onExport() {
+  exportLoading.value = true;
+  try {
+    // 构建导出参数:合并搜索条件、当前tab状态和分页参数
+    const statusParams = statusMap[currentStatus.value] || {};
+    const exportParams = {
+      ...currentSearchParams.value,
+      ...statusParams,
+      // 添加分页参数,与列表保持一致
+      page: pageData.page,
+      size: pageData.size,
+    };
 
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
+    // 处理时间范围搜索
+    if (exportParams.createTime && exportParams.createTime.length === 2) {
+      exportParams.startTime = exportParams.createTime[0];
+      exportParams.endTime = exportParams.createTime[1];
+      delete exportParams.createTime;
+    }
 
-  // table 批量选择
-  function changeSelection(row) {
-    table.selected = row;
+    await api.order.export(exportParams, '订单记录');
+  } catch (error) {
+    console.error('导出订单失败:', error);
+  } finally {
+    exportLoading.value = false;
   }
+}
 
-  // 导出订单/导出发货单
-  const exportLoading = ref(false);
-  async function onExport() {
+async function onExportDelivery() {
+  try {
     exportLoading.value = true;
-    try {
-      // 构建导出参数:合并搜索条件和当前tab状态
-      const statusParams = statusMap[currentStatus.value] || {};
-      const exportParams = { ...currentSearchParams.value, ...statusParams };
-
-      // 处理时间范围搜索
-      if (exportParams.createTime && exportParams.createTime.length === 2) {
-        exportParams.startTime = exportParams.createTime[0];
-        exportParams.endTime = exportParams.createTime[1];
-        delete exportParams.createTime;
-      }
 
-      await api.order.export(exportParams, '订单记录');
-    } catch (error) {
-      console.error('导出订单失败:', error);
-    } finally {
-      exportLoading.value = false;
-    }
-  }
+    // 构建导出发货单参数:合并搜索条件、当前tab状态和分页参数
+    const exportParams = {
+      // 添加分页参数,与列表保持一致(默认先100)
+      page: pageData.page,
+      size: 100 || pageData.size,
+    };
 
-  async function onExportDelivery() {
-    try {
-      exportLoading.value = true;
-      await api.order.exportDelivery({}, '发货订单', 'exportDelivery');
-    } finally {
-      exportLoading.value = false;
+    // 处理时间范围搜索
+    if (exportParams.createTime && exportParams.createTime.length === 2) {
+      exportParams.startTime = exportParams.createTime[0];
+      exportParams.endTime = exportParams.createTime[1];
+      delete exportParams.createTime;
     }
-  }
 
-  const batchHandleTools = [
+    await api.order.exportDelivery(exportParams, '发货订单', 'exportDelivery');
+  } finally {
+    exportLoading.value = false;
+  }
+}
+
+const batchHandleTools = [
+  {
+    operType: 'all',
+    type: 'all',
+    label: '批量发货',
+    buttonType: 'default',
+  },
+];
+
+function batchHandle() {
+  let order_ids = [];
+  table.selected.forEach((s) => {
+    order_ids.push(s.id);
+  });
+  useModal(
+    OrderBatchDispatch,
     {
-      operType: 'all',
-      type: 'all',
-      label: '批量发货',
-      buttonType: 'default',
+      title: '批量发货',
+      order_ids,
     },
-  ];
-
-  function batchHandle() {
-    let order_ids = [];
-    table.selected.forEach((s) => {
-      order_ids.push(s.id);
-    });
-    useModal(
-      OrderBatchDispatch,
-      {
-        title: '批量发货',
-        order_ids,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
+    {
+      close: (result) => {
+        // 如果批量发货成功,刷新当前页面数据
+        getData();
       },
-    );
-  }
+    },
+  );
+}
 
-  function detailRow(id) {
-    useModal(
-      OrderDetail,
-      {
-        title: '订单详情',
-        type: 'detail',
-        id,
+function detailRow(id) {
+  useModal(
+    OrderDetail,
+    {
+      title: '订单详情',
+      type: 'detail',
+      id,
+    },
+    {
+      confirm: () => {
+        getData();
       },
-      {
-        confirm: () => {
-          getData();
-        },
-        close: () => {
-          getData();
-        },
+      close: () => {
+        getData();
       },
-    );
-  }
-
-  async function cancelOrder(id) {
-    try {
-      await ElMessageBox.confirm('确定要取消这个订单吗?', '提示', {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
-        type: 'warning',
-      });
-
-      const { code, message } = await api.order.cancel(id);
-      if (code == 200) {
-        ElMessage.success('订单取消成功');
-        getData(); // 刷新列表
-      } else {
-        ElMessage.error(message || '取消订单失败');
-      }
-    } catch (error) {}
-  }
+    },
+  );
+}
+
+async function cancelOrder(id) {
+  try {
+    await ElMessageBox.confirm('确定要取消这个订单吗?', '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    });
 
-  // 打开用户详情
-  function openUserDetail(uid) {
-    if (uid) {
-      useModal(
-        UserEdit,
-        {
-          title: '编辑用户',
-          type: 'edit',
-          id: uid,
-        },
-        {
-          confirm: () => {
-            // 用户编辑完成后可以选择是否刷新订单列表
-            // getData();
-          },
-        },
-      );
+    const { code, message } = await api.order.cancel(id);
+    if (code == 200) {
+      ElMessage.success('订单取消成功');
+      getData(); // 刷新列表
+    } else {
+      ElMessage.error(message || '取消订单失败');
     }
-  }
+  } catch (error) { }
+}
 
-  function onSend(row) {
+// 打开用户详情
+function openUserDetail(uid) {
+  if (uid) {
     useModal(
-      OrderDispatch,
+      UserEdit,
       {
-        title: '订单发货',
-        data: row,
+        title: '编辑用户',
+        type: 'edit',
+        id: uid,
       },
       {
-        success: () => {
-          getData();
+        confirm: () => {
+          // 用户编辑完成后可以选择是否刷新订单列表
+          // getData();
         },
       },
     );
   }
+}
 
-  onMounted(() => {
-    getData();
-  });
+function onSend(row) {
+  useModal(
+    OrderDispatch,
+    {
+      title: '订单发货',
+      data: row,
+    },
+    {
+      success: () => {
+        getData();
+      },
+    },
+  );
+}
+
+// 监听路由参数变化,同步状态
+watch(
+  () => route.query.status,
+  (newStatus) => {
+    if (newStatus && newStatus !== currentStatus.value) {
+      currentStatus.value = newStatus;
+      // 获取对应状态参数并刷新数据
+      const statusParams = statusMap[newStatus] || {};
+      const mergedParams = { ...currentSearchParams.value, ...statusParams };
+      getData(1, mergedParams);
+    }
+  },
+  { immediate: false }
+);
+
+onMounted(() => {
+  // 初始化时根据URL状态加载数据
+  const statusParams = statusMap[currentStatus.value] || {};
+  getData(1, statusParams);
+});
 </script>
 <style lang="scss" scoped>
-  .order-view {
-    .el-main {
-      .sa-table-wrap {
-        overflow: hidden;
-        height: 100%;
-        :deep() {
-          .el-table__empty-text {
-            margin-left: 0;
-          }
-        }
-        .order-info {
-          .order-sn {
-            font-size: 14px;
-            font-weight: 500;
-            color: var(--sa-font);
-            margin-bottom: 4px;
-            line-height: 1.4;
-          }
-          .order-time {
-            font-size: 12px;
-            color: var(--sa-subfont);
-            line-height: 1.2;
-          }
+.order-view {
+  .el-main {
+    .sa-table-wrap {
+      overflow: hidden;
+      height: 100%;
+
+      :deep() {
+        .el-table__empty-text {
+          margin-left: 0;
         }
-        .goods-title {
+      }
+
+      .order-info {
+        .order-sn {
           font-size: 14px;
           font-weight: 500;
           color: var(--sa-font);
           margin-bottom: 4px;
           line-height: 1.4;
         }
-        .goods-sku {
+
+        .order-time {
           font-size: 12px;
           color: var(--sa-subfont);
           line-height: 1.2;
         }
       }
-    }
 
-    .order-index-main {
-      overflow: hidden;
-    }
+      .goods-title {
+        font-size: 14px;
+        font-weight: 500;
+        color: var(--sa-font);
+        margin-bottom: 4px;
+        line-height: 1.4;
+      }
 
-    .order-index-footer {
-      --el-footer-height: fit-content;
+      .goods-sku {
+        font-size: 12px;
+        color: var(--sa-subfont);
+        line-height: 1.2;
+      }
     }
+  }
 
-    .search-container {
-      .range-input-group {
-        display: flex;
-        align-items: center;
-        gap: 8px;
-        width: 100%;
+  .order-index-main {
+    overflow: hidden;
+  }
 
-        .el-input {
-          flex: 1;
-        }
+  .order-index-footer {
+    --el-footer-height: fit-content;
+  }
 
-        .range-separator {
-          color: var(--el-text-color-regular);
-          font-size: 14px;
-          white-space: nowrap;
-        }
+  .search-container {
+    .range-input-group {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      width: 100%;
+
+      .el-input {
+        flex: 1;
       }
-    }
 
-    // 商品信息样式
-    .goods-title {
-      font-weight: 500;
-      color: #303133;
-      margin-bottom: 4px;
-      line-height: 1.4;
+      .range-separator {
+        color: var(--el-text-color-regular);
+        font-size: 14px;
+        white-space: nowrap;
+      }
     }
+  }
 
-    .goods-info {
-      font-size: 12px;
-      color: #909399;
-      line-height: 1.3;
-    }
+  // 商品信息样式
+  .goods-title {
+    font-weight: 500;
+    color: #303133;
+    margin-bottom: 4px;
+    line-height: 1.4;
+  }
 
-    // 用户名按钮样式
-    .user-name-btn {
-      color: #409eff;
-      text-decoration: none;
-      padding: 0;
-      font-size: 14px;
+  .goods-info {
+    font-size: 12px;
+    color: #909399;
+    line-height: 1.3;
+  }
 
-      &:hover {
-        color: #66b1ff;
-        text-decoration: underline;
-      }
-    }
+  // 用户名按钮样式
+  .user-name-btn {
+    color: #409eff;
+    text-decoration: none;
+    padding: 0;
+    font-size: 14px;
 
-    // 物流信息样式
-    .delivery-name {
-      font-size: 14px;
-      color: #303133;
-      margin-bottom: 2px;
+    &:hover {
+      color: #66b1ff;
+      text-decoration: underline;
     }
+  }
 
-    // 订单号样式
-    .order-sn {
-      font-weight: 500;
-      color: #303133;
-    }
+  // 物流信息样式
+  .delivery-name {
+    font-size: 14px;
+    color: #303133;
+    margin-bottom: 2px;
+  }
+
+  // 订单号样式
+  .order-sn {
+    font-weight: 500;
+    color: #303133;
   }
+}
 </style>

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 543 - 541
src/app/shop/admin/user/list/detail.vue


+ 122 - 152
src/app/shop/admin/user/list/edit.vue

@@ -1,195 +1,165 @@
 <template>
   <el-container>
     <el-main>
-      <el-form
-        :model="form.model"
-        :rules="form.rules"
-        ref="formRef"
-        label-width="100px"
-        v-loading="loading"
-        element-loading-text="加载中..."
-      >
-        <el-form-item label="用户手机" prop="phoneNo">
-          <el-input v-model="form.model.phoneNo" placeholder="请输入用户手机"></el-input>
+      <el-form :model="form.model" :rules="form.rules" ref="formRef" :label-width="formLabelWidth" v-loading="loading"
+        :element-loading-text="t('common.loading')">
+        <el-form-item :label="t('modules.user.userPhone')" prop="phoneNo">
+          <el-input v-model="form.model.phoneNo" :placeholder="t('form.inputUserPhone')"></el-input>
         </el-form-item>
-        <el-form-item label="用户昵称" prop="nickname">
-          <el-input v-model="form.model.nickname" placeholder="请输入用户昵称"></el-input>
+        <el-form-item :label="t('modules.user.userNickname')" prop="nickname">
+          <el-input v-model="form.model.nickname" :placeholder="t('form.inputUserNickname')"></el-input>
         </el-form-item>
 
-        <el-form-item label="用户头像" prop="headPic">
-          <sa-upload-image
-            v-model="form.model.headPic"
-            :max-count="1"
-            :accept="['jpg', 'png']"
-            :max-size="5"
-            :direct-upload="true"
-            :size="100"
-            field5
-            placeholder="上传用户头像"
-          />
+        <el-form-item :label="t('modules.user.userAvatar')" prop="headPic">
+          <sa-upload-image v-model="form.model.headPic" :max-count="1" :accept="['jpg', 'png']" :max-size="5"
+            :direct-upload="true" :size="100" field5 :placeholder="t('form.uploadUserAvatar')" />
         </el-form-item>
 
-        <el-form-item label="收款银行" prop="bank">
-          <el-input v-model="form.model.bank" placeholder="请输入收款银行"></el-input>
+        <el-form-item :label="t('modules.user.paymentBank')" prop="bank">
+          <el-input v-model="form.model.bank" :placeholder="t('form.inputPaymentBank')"></el-input>
         </el-form-item>
 
-        <el-form-item label="账户名称" prop="bankAccountName">
-          <el-input v-model="form.model.bankAccountName" placeholder="请输入账户名称"></el-input>
+        <el-form-item :label="t('modules.user.accountName')" prop="bankAccountName">
+          <el-input v-model="form.model.bankAccountName" :placeholder="t('form.inputAccountName')"></el-input>
         </el-form-item>
 
-        <el-form-item label="收款账户" prop="bankAccount">
-          <el-input v-model="form.model.bankAccount" placeholder="请输入收款账户"></el-input>
+        <el-form-item :label="t('modules.user.paymentAccount')" prop="bankAccount">
+          <el-input v-model="form.model.bankAccount" :placeholder="t('form.inputPaymentAccount')"></el-input>
         </el-form-item>
 
         <!-- 权限设置 -->
-        <el-form-item label="登录权限" prop="hasLogin">
-          <el-switch
-            v-model="form.model.hasLogin"
-            :active-value="1"
-            :inactive-value="2"
-            active-text="允许登录"
-            inactive-text="禁止登录"
-            inline-prompt
-          />
+        <el-form-item :label="t('modules.user.loginPermission')" prop="hasLogin">
+          <el-switch v-model="form.model.hasLogin" :active-value="1" :inactive-value="2"
+            :active-text="t('common.allow') + t('modules.user.login')"
+            :inactive-text="t('common.forbid') + t('modules.user.login')" inline-prompt />
         </el-form-item>
 
-        <el-form-item label="下单权限" prop="hasOrder">
-          <el-switch
-            v-model="form.model.hasOrder"
-            :active-value="1"
-            :inactive-value="2"
-            active-text="允许下单"
-            inactive-text="禁止下单"
-            inline-prompt
-          />
+        <el-form-item :label="t('modules.user.orderPermission')" prop="hasOrder">
+          <el-switch v-model="form.model.hasOrder" :active-value="1" :inactive-value="2"
+            :active-text="t('common.allow') + t('modules.user.order')"
+            :inactive-text="t('common.forbid') + t('modules.user.order')" inline-prompt />
         </el-form-item>
 
-        <el-form-item label="提现权限" prop="hasWithdraw">
-          <el-switch
-            v-model="form.model.hasWithdraw"
-            :active-value="1"
-            :inactive-value="2"
-            active-text="允许提现"
-            inactive-text="禁止提现"
-            inline-prompt
-          />
+        <el-form-item :label="t('modules.user.withdrawPermission')" prop="hasWithdraw">
+          <el-switch v-model="form.model.hasWithdraw" :active-value="1" :inactive-value="2"
+            :active-text="t('common.allow') + t('modules.user.withdraw')"
+            :inactive-text="t('common.forbid') + t('modules.user.withdraw')" inline-prompt />
         </el-form-item>
 
-        <el-form-item label="备注" prop="memo">
-          <el-input
-            v-model="form.model.memo"
-            type="textarea"
-            :rows="4"
-            maxlength="100"
-            show-word-limit
-            placeholder="请输入备注(限100字)"
-          ></el-input>
+        <el-form-item :label="t('common.memo')" prop="memo">
+          <el-input v-model="form.model.memo" type="textarea" :rows="4" maxlength="100" show-word-limit
+            :placeholder="t('form.inputMemo')"></el-input>
         </el-form-item>
       </el-form>
     </el-main>
     <el-footer class="sa-footer--submit">
-      <el-button v-if="modal.params.type == 'add'" type="primary" @click="confirm">保存</el-button>
-      <el-button v-if="modal.params.type == 'edit'" type="primary" @click="confirm">更新</el-button>
+      <el-button v-if="modal.params.type == 'add'" type="primary" @click="confirm">{{ t('common.save') }}</el-button>
+      <el-button v-if="modal.params.type == 'edit'" type="primary" @click="confirm">{{ t('common.update') }}</el-button>
     </el-footer>
   </el-container>
 </template>
 <script setup>
-  import { cloneDeep } from 'lodash';
-  import { onMounted, reactive, ref, unref } from 'vue';
-  import { api } from '../user.service';
-  import { ElMessage } from 'element-plus';
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
-  // 添加 编辑 form
-  let formRef = ref(null);
-  const form = reactive({
-    model: {
-      nickname: '',
-      phoneNo: '',
-      headPic: [],
-      bank: '',
-      bankAccountName: '',
-      bankAccount: '',
-      hasLogin: 1, // 登录权限:1是 2否
-      hasOrder: 1, // 下单权限:1是 2否
-      hasWithdraw: 1, // 提现权限:1是 2否
-      memo: '',
-    },
-    rules: {
-      // nickname: [{ required: true, message: '请填写用户昵称', trigger: 'blur' }],
-      phoneNo: [{ required: true, message: '请填写手机号', trigger: 'blur' }],
-      // headPic: [{ required: true, message: '请上传用户头像', trigger: 'change' }],
-      hasLogin: [{ required: true, message: '请设置登录权限', trigger: 'change' }],
-      hasOrder: [{ required: true, message: '请设置下单权限', trigger: 'change' }],
-      hasWithdraw: [{ required: true, message: '请设置提现权限', trigger: 'change' }],
-      memo: [{ max: 100, message: '备注不能超过100字', trigger: 'blur' }],
-    },
-  });
-  const loading = ref(false);
-  // 获取详情
-  async function getDetail(id) {
-    loading.value = true;
-    const { code, data } = await api.list.userDetail(id);
-    if (code == 200) {
-      // 处理头像数据
-      if (data.headPic) {
-        form.model.headPic = Array.isArray(data.headPic) ? data.headPic : [data.headPic];
-      }
-      // 其他字段直接赋值
-      Object.keys(form.model).forEach((key) => {
-        if (key !== 'headPic' && data[key] !== undefined) {
-          form.model[key] = data[key];
-        }
-      });
+import { cloneDeep } from 'lodash';
+import { onMounted, reactive, ref, unref } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useFormConfig } from '@/hooks/useFormConfig';
+import { api } from '../user.service';
+import { ElMessage } from 'element-plus';
+
+const { t } = useI18n();
+const { formLabelWidth } = useFormConfig({ enWidth: '180px' });
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps({
+  modal: {
+    type: Object,
+  },
+});
+// 添加 编辑 form
+let formRef = ref(null);
+const form = reactive({
+  model: {
+    nickname: '',
+    phoneNo: '',
+    headPic: [],
+    bank: '',
+    bankAccountName: '',
+    bankAccount: '',
+    hasLogin: 1, // 登录权限:1是 2否
+    hasOrder: 1, // 下单权限:1是 2否
+    hasWithdraw: 1, // 提现权限:1是 2否
+    memo: '',
+  },
+  rules: {
+    // nickname: [{ required: true, message: '请填写用户昵称', trigger: 'blur' }],
+    phoneNo: [{ required: true, message: '请填写手机号', trigger: 'blur' }],
+    // headPic: [{ required: true, message: '请上传用户头像', trigger: 'change' }],
+    hasLogin: [{ required: true, message: '请设置登录权限', trigger: 'change' }],
+    hasOrder: [{ required: true, message: '请设置下单权限', trigger: 'change' }],
+    hasWithdraw: [{ required: true, message: '请设置提现权限', trigger: 'change' }],
+    memo: [{ max: 100, message: '备注不能超过100字', trigger: 'blur' }],
+  },
+});
+const loading = ref(false);
+// 获取详情
+async function getDetail(id) {
+  loading.value = true;
+  const { code, data } = await api.list.userDetail(id);
+  if (code == 200) {
+    // 处理头像数据
+    if (data.headPic) {
+      form.model.headPic = Array.isArray(data.headPic) ? data.headPic : [data.headPic];
     }
-    loading.value = false;
+    // 其他字段直接赋值
+    Object.keys(form.model).forEach((key) => {
+      if (key !== 'headPic' && data[key] !== undefined) {
+        form.model[key] = data[key];
+      }
+    });
   }
+  loading.value = false;
+}
 
-  // 表单关闭时提交
-  async function confirm() {
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-      let submitForm = cloneDeep(form.model);
+// 表单关闭时提交
+async function confirm() {
+  unref(formRef).validate(async (valid) => {
+    if (!valid) return;
+    let submitForm = cloneDeep(form.model);
 
-      // 处理头像数据 - 只提交第一张图片的URL
-      if (submitForm.headPic && submitForm.headPic.length > 0) {
-        submitForm.headPic = submitForm.headPic[0];
-      } else {
-        submitForm.headPic = '';
-      }
+    // 处理头像数据 - 只提交第一张图片的URL
+    if (submitForm.headPic && submitForm.headPic.length > 0) {
+      submitForm.headPic = submitForm.headPic[0];
+    } else {
+      submitForm.headPic = '';
+    }
 
-      const { code } =
-        props.modal.params.type == 'add'
-          ? await api.list.add(submitForm)
-          : await api.list.edit({ id: props.modal.params.id, ...submitForm });
-      if (code == '200') {
-        ElMessage.success('保存成功');
-        emit('modalCallBack', { event: 'confirm' });
-      }
-    });
-  }
-  async function init() {
-    if (props.modal.params.id) {
-      await getDetail(props.modal.params.id);
+    const { code } =
+      props.modal.params.type == 'add'
+        ? await api.list.add(submitForm)
+        : await api.list.edit({ id: props.modal.params.id, ...submitForm });
+    if (code == '200') {
+      ElMessage.success('保存成功');
+      emit('modalCallBack', { event: 'confirm' });
     }
-  }
-  onMounted(() => {
-    init();
   });
+}
+async function init() {
+  if (props.modal.params.id) {
+    await getDetail(props.modal.params.id);
+  }
+}
+onMounted(() => {
+  init();
+});
 </script>
 
 <style lang="scss" scoped>
-  .el-form {
-    max-width: 600px;
+.el-form {
+  max-width: 600px;
 
-    .el-checkbox-group {
-      .el-checkbox {
-        margin-right: 15px;
-      }
+  .el-checkbox-group {
+    .el-checkbox {
+      margin-right: 15px;
     }
   }
+}
 </style>

+ 289 - 313
src/app/shop/admin/user/list/index.vue

@@ -3,24 +3,13 @@
     <el-header class="sa-header">
       <!-- 搜索组件 -->
       <div class="search-container">
-        <sa-search-simple
-          :searchFields="searchFields"
-          :defaultValues="defaultSearchValues"
-          @search="handleSearch"
-          @reset="handleReset"
-        >
+        <sa-search-simple :searchFields="searchFields" :defaultValues="defaultSearchValues" @search="handleSearch"
+          @reset="handleReset">
           <template #custom="{ data }">
             <el-form-item :label="t('modules.user.registrationTime')">
-              <el-date-picker
-                v-model="data.dateRange"
-                type="daterange"
-                :range-separator="t('common.to')"
-                :start-placeholder="t('common.startDate')"
-                :end-placeholder="t('common.endDate')"
-                format="YYYY-MM-DD"
-                value-format="YYYY-MM-DD"
-                style="width: 240px"
-              />
+              <el-date-picker v-model="data.dateRange" type="daterange" :range-separator="t('common.to')"
+                :start-placeholder="t('common.startDate')" :end-placeholder="t('common.endDate')" format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD" style="width: 240px" />
             </el-form-item>
           </template>
         </sa-search-simple>
@@ -32,13 +21,8 @@
         </div>
         <div>
           <el-button class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
-          <el-button
-            icon="Download"
-            type="primary"
-            :loading="exportLoading"
-            :disabled="exportLoading"
-            @click="exportUsers"
-          >
+          <el-button icon="Download" type="primary" :loading="exportLoading" :disabled="exportLoading"
+            @click="exportUsers">
             {{ exportLoading ? t('common.exporting') : t('common.exportData') }}
           </el-button>
         </div>
@@ -46,107 +30,100 @@
     </el-header>
     <el-main class="sa-p-0">
       <div class="sa-table-wrap" v-loading="loading">
-        <el-table
-          height="100%"
-          class="sa-table"
-          :data="table.data"
-          @selection-change="changeSelection"
-          @sort-change="fieldFilter"
-          row-key="id"
-          stripe
-        >
+        <el-table height="100%" class="sa-table" :data="table.data" @selection-change="changeSelection"
+          @sort-change="fieldFilter" row-key="id" stripe>
           <template #empty>
             <sa-empty />
           </template>
           <el-table-column type="selection" width="48" align="center"></el-table-column>
-          <el-table-column prop="id" label="用户ID" min-width="120" sortable="custom">
+          <el-table-column prop="id" :label="t('modules.user.userId')" min-width="120" sortable="custom">
             <template #default="scope">
               <span class="user-id">{{ scope.row.id }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="用户名" min-width="140">
+          <el-table-column :label="t('modules.user.userName')" min-width="120">
             <template #default="scope">
               <div class="user-info">
                 {{ scope.row.name || '-' }}
               </div>
             </template>
           </el-table-column>
-          <el-table-column label="手机号码" min-width="140">
+          <el-table-column :label="t('modules.user.userPhone')" min-width="120">
             <template #default="scope">
               {{ scope.row.phoneNo || '-' }}
             </template>
           </el-table-column>
-          <el-table-column label="用户等级" min-width="100" align="center">
+          <el-table-column :label="t('modules.user.userLevel')" min-width="100" align="center">
             <template #default="scope">
               <el-tag type="primary" size="small">V{{ scope.row.vipLevel || '-' }}</el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="邀请好友数" min-width="120" align="center">
+          <el-table-column :label="t('modules.user.inviteFriends')" min-width="120" align="center">
             <template #default="scope">
               {{ scope.row.inviteNum || 0 }}
             </template>
           </el-table-column>
-          <el-table-column label="团队人数" min-width="120" align="center">
+          <el-table-column :label="t('modules.user.teamMembers')" min-width="130" align="center">
             <template #default="scope">
               {{ scope.row.teamNum || 0 }}
             </template>
           </el-table-column>
-          <el-table-column label="成功订单" min-width="120" align="center">
+          <el-table-column :label="t('modules.user.successOrders')" min-width="120" align="center">
             <template #default="scope">
               {{ scope.row.successGroupNum || 0 }}
             </template>
           </el-table-column>
-          <el-table-column label="账户余额" min-width="120" align="center">
+          <el-table-column :label="t('modules.user.accountBalance')" min-width="130" align="center">
             <template #default="scope">
               <span class="amount">{{ scope.row.walletBalance || '৳0' }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="佣金余额" min-width="120" align="center">
+          <el-table-column :label="t('modules.user.commissionBalance')" min-width="160" align="center">
             <template #default="scope">
               <span class="amount">{{ scope.row.earningsBalance || '৳0' }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="登录权限" min-width="100" align="center">
+          <el-table-column :label="t('modules.user.loginPermission')" min-width="140" align="center">
             <template #default="scope">
               <el-tag :type="scope.row.hasLogin === 1 ? 'success' : 'danger'" size="small">
-                {{ scope.row.hasLogin === 1 ? '允许' : '禁止' }}
+                {{ scope.row.hasLogin === 1 ? t('common.allow') : t('common.forbid') }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="下单权限" min-width="100" align="center">
+          <el-table-column :label="t('modules.user.orderPermission')" min-width="140" align="center">
             <template #default="scope">
               <el-tag :type="scope.row.hasOrder === 1 ? 'success' : 'danger'" size="small">
-                {{ scope.row.hasOrder === 1 ? '允许' : '禁止' }}
+                {{ scope.row.hasOrder === 1 ? t('common.allow') : t('common.forbid') }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="提现权限" min-width="100" align="center">
+          <el-table-column :label="t('modules.user.withdrawPermission')" min-width="170" align="center">
             <template #default="scope">
               <el-tag :type="scope.row.hasWithdraw === 1 ? 'success' : 'danger'" size="small">
-                {{ scope.row.hasWithdraw === 1 ? '允许' : '禁止' }}
+                {{ scope.row.hasWithdraw === 1 ? t('common.allow') : t('common.forbid') }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="备注" min-width="120">
+          <el-table-column :label="t('common.memo')" min-width="120">
             <template #default="scope">
               {{ scope.row.memo || '--' }}
             </template>
           </el-table-column>
-          <el-table-column label="注册时间" min-width="160">
+          <el-table-column :label="t('modules.user.registrationTime')" min-width="160">
             <template #default="scope">
               {{ scope.row.createTime || '-' }}
             </template>
           </el-table-column>
-          <el-table-column fixed="right" label="操作" min-width="200">
+          <el-table-column fixed="right" :label="t('common.actions')" min-width="250">
             <template #default="scope">
               <el-button class="is-link" type="primary" @click="viewDetail(scope.row)">
-                详情
+                {{ t('common.detail') }}
               </el-button>
               <el-button class="is-link" type="primary" @click="editRow(scope.row)">
-                编辑
+                {{ t('common.edit') }}
               </el-button>
               <el-button class="is-link" type="warning" @click="changePassword(scope.row)">
-                改密码
+                {{ t('modules.user.changePassword') }}
               </el-button>
             </template>
           </el-table-column>
@@ -155,11 +132,8 @@
     </el-main>
     <sa-view-bar>
       <template #left>
-        <sa-batch-handle
-          :batchHandleTools="batchHandleTools"
-          :selectedLeng="table.selected.length"
-          @batchHandle="batchHandle"
-        ></sa-batch-handle>
+        <sa-batch-handle :batchHandleTools="batchHandleTools" :selectedLeng="table.selected.length"
+          @batchHandle="batchHandle"></sa-batch-handle>
       </template>
       <template #right>
         <sa-pagination :pageData="pageData" @updateFn="getData" />
@@ -168,295 +142,297 @@
   </el-container>
 </template>
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../user.service';
-  import { ElMessageBox, ElMessage } from 'element-plus';
-  import { useModal } from '@/sheep/hooks';
-  import { usePagination } from '@/sheep/hooks';
-  import { useI18n } from 'vue-i18n';
+import { onMounted, reactive, ref } from 'vue';
+import { api } from '../user.service';
+import { ElMessageBox, ElMessage } from 'element-plus';
+import { useModal } from '@/sheep/hooks';
+import { usePagination } from '@/sheep/hooks';
+import { useI18n } from 'vue-i18n';
 
-  const { t } = useI18n();
-  import userEdit from './edit.vue';
-  import userDetail from './detail.vue';
-  import userPassword from './password.vue';
-  const { pageData } = usePagination();
+const { t } = useI18n();
+import userEdit from './edit.vue';
+import userDetail from './detail.vue';
+import userPassword from './password.vue';
+const { pageData } = usePagination();
 
-  // 搜索字段配置
-  const searchFields = reactive({
-    name: {
-      type: 'input',
-      get label() {
-        return t('modules.user.userAccount');
-      },
-      get placeholder() {
-        return t('form.inputUserAccount');
-      },
-      width: 200,
+// 搜索字段配置
+const searchFields = reactive({
+  name: {
+    type: 'input',
+    get label() {
+      return t('modules.user.userAccount');
     },
-    phone: {
-      type: 'input',
-      get label() {
-        return t('modules.user.userPhone');
-      },
-      get placeholder() {
-        return t('form.inputPhone');
-      },
-      width: 150,
+    get placeholder() {
+      return t('form.inputUserAccount');
     },
-    hasLogin: {
-      type: 'select',
-      get label() {
-        return t('modules.user.loginPermission');
-      },
-      get placeholder() {
-        return t('form.selectLoginPermission');
-      },
-      width: 120,
-      get options() {
-        return [
-          { label: t('common.all'), value: '' },
-          { label: t('common.allow'), value: 1 },
-          { label: t('common.forbid'), value: 2 },
-        ];
-      },
+    width: 200,
+  },
+  phone: {
+    type: 'input',
+    get label() {
+      return t('modules.user.userPhone');
     },
-    hasOrder: {
-      type: 'select',
-      get label() {
-        return t('modules.user.orderPermission');
-      },
-      get placeholder() {
-        return t('form.selectOrderPermission');
-      },
-      width: 120,
-      get options() {
-        return [
-          { label: t('common.all'), value: '' },
-          { label: t('common.allow'), value: 1 },
-          { label: t('common.forbid'), value: 2 },
-        ];
-      },
+    get placeholder() {
+      return t('form.inputPhone');
     },
-    hasWithdraw: {
-      type: 'select',
-      get label() {
-        return t('modules.user.withdrawPermission');
-      },
-      get placeholder() {
-        return t('form.selectWithdrawPermission');
-      },
-      width: 120,
-      get options() {
-        return [
-          { label: t('common.all'), value: '' },
-          { label: t('common.allow'), value: 1 },
-          { label: t('common.forbid'), value: 2 },
-        ];
-      },
+    width: 150,
+  },
+  hasLogin: {
+    type: 'select',
+    get label() {
+      return t('modules.user.loginPermission');
     },
-  });
+    get placeholder() {
+      return t('form.selectLoginPermission');
+    },
+    width: 120,
+    get options() {
+      return [
+        { label: t('common.all'), value: '' },
+        { label: t('common.allow'), value: 1 },
+        { label: t('common.forbid'), value: 2 },
+      ];
+    },
+  },
+  hasOrder: {
+    type: 'select',
+    get label() {
+      return t('modules.user.orderPermission');
+    },
+    get placeholder() {
+      return t('form.selectOrderPermission');
+    },
+    width: 120,
+    get options() {
+      return [
+        { label: t('common.all'), value: '' },
+        { label: t('common.allow'), value: 1 },
+        { label: t('common.forbid'), value: 2 },
+      ];
+    },
+  },
+  hasWithdraw: {
+    type: 'select',
+    get label() {
+      return t('modules.user.withdrawPermission');
+    },
+    get placeholder() {
+      return t('form.selectWithdrawPermission');
+    },
+    width: 120,
+    get options() {
+      return [
+        { label: t('common.all'), value: '' },
+        { label: t('common.allow'), value: 1 },
+        { label: t('common.forbid'), value: 2 },
+      ];
+    },
+  },
+});
 
-  // 默认搜索值
-  const defaultSearchValues = reactive({
-    name: '',
-    phone: '',
-    hasLogin: '',
-    hasOrder: '',
-    hasWithdraw: '',
-  });
+// 默认搜索值
+const defaultSearchValues = reactive({
+  name: '',
+  phone: '',
+  hasLogin: '',
+  hasOrder: '',
+  hasWithdraw: '',
+});
 
-  // 列表
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-  const loading = ref(true);
+// 列表
+const table = reactive({
+  data: [],
+  order: '',
+  sort: '',
+  selected: [],
+});
+const loading = ref(true);
 
-  // 获取数据
-  async function getData(page, searchParams = {}) {
-    if (page) pageData.page = page;
-    loading.value = true;
+// 获取数据
+async function getData(page, searchParams = {}) {
+  if (page) pageData.page = page;
+  loading.value = true;
 
-    // 构建请求参数
-    const requestData = {
-      page: pageData.page,
-      size: pageData.size,
-      type: 0,
-      ...searchParams,
-    };
-    // 处理时间范围搜索
-    if (searchParams.dateRange && searchParams.dateRange.length === 2) {
-      requestData.startTime = searchParams.dateRange[0];
-      requestData.endTime = searchParams.dateRange[1];
-      delete requestData.dateRange;
-    }
+  // 构建请求参数
+  const requestData = {
+    page: pageData.page,
+    size: pageData.size,
+    type: 0,
+    ...searchParams,
+  };
+  // 处理时间范围搜索
+  if (searchParams.dateRange && searchParams.dateRange.length === 2) {
+    requestData.startTime = searchParams.dateRange[0];
+    requestData.endTime = searchParams.dateRange[1];
+    delete requestData.dateRange;
+  }
 
-    const { code, data } = await api.list.list(requestData, false);
+  const { code, data } = await api.list.list(requestData, false);
 
-    if (code == '200') {
-      table.data = data.list;
-      pageData.page = data.pageNum;
-      pageData.size = data.pageSize;
-      pageData.total = data.total;
-    }
-    loading.value = false;
+  if (code == '200') {
+    table.data = data.list;
+    pageData.page = data.pageNum;
+    pageData.size = data.pageSize;
+    pageData.total = data.total;
   }
+  loading.value = false;
+}
 
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
-  //table批量选择
-  function changeSelection(row) {
-    table.selected = row;
-  }
-  // 分页/批量操作
-  const batchHandleTools = [
+// table 字段排序
+function fieldFilter({ prop, order }) {
+  table.order = order == 'ascending' ? 'asc' : 'desc';
+  table.sort = prop;
+  getData();
+}
+//table批量选择
+function changeSelection(row) {
+  table.selected = row;
+}
+// 分页/批量操作
+const batchHandleTools = [
+  {
+    type: 'delete',
+    get label() {
+      return t('common.delete');
+    },
+    auth: 'shop.admin.user.list.delete',
+    class: 'danger',
+  },
+];
+// 查看详情
+function viewDetail(row) {
+  useModal(
+    userDetail,
     {
-      type: 'delete',
-      label: '删除',
-      auth: 'shop.admin.user.list.delete',
-      class: 'danger',
+      title: t('modules.user.userDetail'),
+      type: 'view',
+      width: '80%',
+      id: row.id,
     },
-  ];
-  // 查看详情
-  function viewDetail(row) {
-    useModal(
-      userDetail,
-      {
-        title: '用户详情',
-        type: 'view',
-        width: '80%',
-        id: row.id,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
+    {
+      confirm: () => {
+        getData();
       },
-    );
-  }
+    },
+  );
+}
 
-  function addRow() {
-    useModal(
-      userEdit,
-      { title: '新建用户', type: 'add' },
-      {
-        confirm: () => {
-          getData();
-        },
+function addRow() {
+  useModal(
+    userEdit,
+    { title: t('modules.user.addUser'), type: 'add' },
+    {
+      confirm: () => {
+        getData();
       },
-    );
-  }
-
-  // 当前搜索参数状态
-  const currentSearchParams = ref({});
+    },
+  );
+}
 
-  // 导出loading状态
-  const exportLoading = ref(false);
+// 当前搜索参数状态
+const currentSearchParams = ref({});
 
-  // 更新搜索参数处理函数
-  function handleSearch(searchParams) {
-    // 保存当前搜索参数
-    currentSearchParams.value = { ...searchParams };
-    // 执行搜索
-    getData(1, searchParams);
-  }
+// 导出loading状态
+const exportLoading = ref(false);
 
-  // 重置搜索参数处理函数
-  function handleReset() {
-    // 清空当前搜索参数
-    currentSearchParams.value = {};
-    // 执行重置
-    getData(1);
-  }
+// 更新搜索参数处理函数
+function handleSearch(searchParams) {
+  // 保存当前搜索参数
+  currentSearchParams.value = { ...searchParams };
+  // 执行搜索
+  getData(1, searchParams);
+}
 
-  // 导出用户数据
-  async function exportUsers() {
-    if (exportLoading.value) return; // 防止重复点击
+// 重置搜索参数处理函数
+function handleReset() {
+  // 清空当前搜索参数
+  currentSearchParams.value = {};
+  // 执行重置
+  getData(1);
+}
 
-    exportLoading.value = true;
-    try {
-      // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
-      const exportParams = {
-        type: 0, // 固定参数
-        ...currentSearchParams.value, // 当前搜索参数
-      };
+// 导出用户数据
+async function exportUsers() {
+  if (exportLoading.value) return; // 防止重复点击
 
-      // 处理时间范围搜索(与 getData 函数保持一致)
-      if (exportParams.dateRange && exportParams.dateRange.length === 2) {
-        exportParams.startTime = exportParams.dateRange[0];
-        exportParams.endTime = exportParams.dateRange[1];
-        delete exportParams.dateRange;
-      }
+  exportLoading.value = true;
+  try {
+    // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
+    const exportParams = {
+      type: 0, // 固定参数
+      ...currentSearchParams.value, // 当前搜索参数
+    };
 
-      // 调用导出API,所有下载逻辑都在REPORT函数中处理
-      await api.list.report(exportParams, '用户数据');
-    } finally {
-      exportLoading.value = false;
+    // 处理时间范围搜索(与 getData 函数保持一致)
+    if (exportParams.dateRange && exportParams.dateRange.length === 2) {
+      exportParams.startTime = exportParams.dateRange[0];
+      exportParams.endTime = exportParams.dateRange[1];
+      delete exportParams.dateRange;
     }
-  }
 
-  function editRow(row) {
-    useModal(
-      userEdit,
-      {
-        title: '编辑用户',
-        type: 'edit',
-        id: row.id,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
+    // 调用导出API,所有下载逻辑都在REPORT函数中处理
+    await api.list.report(exportParams, '用户数据');
+  } finally {
+    exportLoading.value = false;
   }
+}
 
-  function changePassword(row) {
-    useModal(
-      userPassword,
-      {
-        title: '修改密码',
-        width: '500px',
-        id: row.id,
+function editRow(row) {
+  useModal(
+    userEdit,
+    {
+      title: t('modules.user.editUser'),
+      type: 'edit',
+      id: row.id,
+    },
+    {
+      confirm: () => {
+        getData();
       },
-      {
-        confirm: () => {
-          // 密码修改成功后不需要刷新列表
-        },
+    },
+  );
+}
+
+function changePassword(row) {
+  useModal(
+    userPassword,
+    {
+      title: t('modules.user.changePassword'),
+      width: '500px',
+      id: row.id,
+    },
+    {
+      confirm: () => {
+        // 密码修改成功后不需要刷新列表
       },
-    );
-  }
-  // 删除api 单独批量可以直接调用
-  async function deleteApi(id) {
-    await api.list.delete(id);
-    getData();
-  }
-  async function batchHandle(type) {
-    let ids = [];
-    table.selected.forEach((row) => {
-      ids.push(row.id);
-    });
-    switch (type) {
-      case 'delete':
-        ElMessageBox.confirm('此操作将删除, 是否继续?', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning',
-        }).then(() => {
-          deleteApi(ids.join(','));
-        });
-        break;
-    }
+    },
+  );
+}
+// 删除api 单独批量可以直接调用
+async function deleteApi(id) {
+  await api.list.delete(id);
+  getData();
+}
+async function batchHandle(type) {
+  let ids = [];
+  table.selected.forEach((row) => {
+    ids.push(row.id);
+  });
+  switch (type) {
+    case 'delete':
+      ElMessageBox.confirm(t('message.deleteConfirm'), t('common.tip'), {
+        confirmButtonText: t('common.confirm'),
+        cancelButtonText: t('common.cancel'),
+        type: 'warning',
+      }).then(() => {
+        deleteApi(ids.join(','));
+      });
+      break;
   }
+}
 
-  onMounted(() => {
-    getData();
-  });
+onMounted(() => {
+  getData();
+});
 </script>
 <style lang="scss" scoped></style>

+ 72 - 81
src/app/shop/admin/user/list/password.vue

@@ -1,104 +1,95 @@
 <template>
   <el-container>
     <el-main>
-      <el-form
-        ref="formRef"
-        :model="form.model"
-        :rules="form.rules"
-        label-width="100px"
-        v-loading="loading"
-        element-loading-text="提交中..."
-      >
-        <el-form-item label="新密码" prop="password">
-          <el-input
-            v-model="form.model.password"
-            type="password"
-            placeholder="请输入新密码"
-            show-password
-          ></el-input>
+      <el-form ref="formRef" :model="form.model" :rules="form.rules" :label-width="formLabelWidth" v-loading="loading"
+        :element-loading-text="t('common.submitting')">
+        <el-form-item :label="t('modules.user.newPassword')" prop="password">
+          <el-input v-model="form.model.password" type="password" :placeholder="t('form.inputNewPassword')"
+            show-password></el-input>
         </el-form-item>
 
-        <el-form-item label="确认密码" prop="confirmPassword">
-          <el-input
-            v-model="form.model.confirmPassword"
-            type="password"
-            placeholder="请再次输入新密码"
-            show-password
-          ></el-input>
+        <el-form-item :label="t('modules.user.confirmPassword')" prop="confirmPassword">
+          <el-input v-model="form.model.confirmPassword" type="password" :placeholder="t('form.inputConfirmPassword')"
+            show-password></el-input>
         </el-form-item>
       </el-form>
     </el-main>
     <el-footer class="sa-footer--submit">
-      <el-button type="primary" @click="onConfirm">更新</el-button>
+      <el-button type="primary" @click="onConfirm">{{ t('common.update') }}</el-button>
     </el-footer>
   </el-container>
 </template>
 
 <script setup>
-  import { reactive, ref } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import { api } from '../user.service';
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-      default: () => ({}),
-    },
-  });
+import { reactive, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useFormConfig } from '@/hooks/useFormConfig';
+import { ElMessage } from 'element-plus';
+import { api } from '../user.service';
 
-  const formRef = ref();
-  const loading = ref(false);
+const { t } = useI18n();
+const { formLabelWidth } = useFormConfig({ enWidth: '160px' });
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps({
+  modal: {
+    type: Object,
+    default: () => ({}),
+  },
+});
 
-  const form = reactive({
-    model: {
-      password: '',
-      confirmPassword: '',
-    },
-    rules: {
-      password: [
-        { required: true, message: '请输入新密码', trigger: 'blur' },
-        { min: 6, max: 20, message: '密码长度为6-20位', trigger: 'blur' },
-      ],
-      confirmPassword: [
-        { required: true, message: '请再次输入新密码', trigger: 'blur' },
-        {
-          validator: (rule, value, callback) => {
-            if (value !== form.model.password) {
-              callback(new Error('两次输入的密码不一致'));
-            } else {
-              callback();
-            }
-          },
-          trigger: 'blur',
+const formRef = ref();
+const loading = ref(false);
+
+const form = reactive({
+  model: {
+    password: '',
+    confirmPassword: '',
+  },
+  rules: {
+    password: [
+      { required: true, get message() { return t('form.passwordRequired'); }, trigger: 'blur' },
+      { min: 6, max: 20, get message() { return t('form.passwordLength'); }, trigger: 'blur' },
+    ],
+    confirmPassword: [
+      { required: true, get message() { return t('form.confirmPasswordRequired'); }, trigger: 'blur' },
+      {
+        validator: (rule, value, callback) => {
+          if (value !== form.model.password) {
+            callback(new Error(t('form.passwordMismatch')));
+          } else {
+            callback();
+          }
         },
-      ],
-    },
-  });
+        trigger: 'blur',
+      },
+    ],
+  },
+});
 
-  // 提交表单
-  const onConfirm = async () => {
-    try {
-      await formRef.value.validate();
+// 提交表单
+const onConfirm = async () => {
+  try {
+    await formRef.value.validate();
 
-      loading.value = true;
-      const { code } = await api.list.edit({
-        id: props.modal.params.id,
-        pwd: form.model.password,
-      });
+    loading.value = true;
+    const { code } = await api.list.edit({
+      id: props.modal.params.id,
+      pwd: form.model.password,
+    });
 
-      if (code == '200') {
-        ElMessage.success('密码修改成功');
-        emit('modalCallBack', { event: 'confirm' });
-      }
-    } catch (error) {
-      console.error('修改密码失败:', error);
-      return false;
-    } finally {
-      loading.value = false;
+    if (code == '200') {
+      ElMessage.success(t('message.passwordUpdateSuccess'));
+      emit('modalCallBack', { event: 'confirm' });
     }
-  };
+  } catch (error) {
+    console.error('修改密码失败:', error);
+    return false;
+  } finally {
+    loading.value = false;
+  }
+};
 
-  defineExpose({
-    onConfirm,
-  });
+defineExpose({
+  onConfirm,
+});
 </script>

+ 128 - 0
src/hooks/useNavigationConfig.js

@@ -0,0 +1,128 @@
+import { computed } from 'vue';
+import { getCurrentLanguage } from '@/locales';
+
+/**
+ * 导航栏配置hooks
+ * 提供响应式的导航栏配置,根据当前语言自动调整宽度和样式
+ * @param {Object} options - 配置选项
+ * @param {string} options.zhWidth - 中文导航总宽度,默认 '200px'
+ * @param {string} options.enWidth - 英文导航总宽度,默认 '260px'
+ * @param {Object} options.menu1Width - 主菜单宽度配置
+ * @param {Object} options.menu2Width - 子菜单宽度配置
+ * @param {Object} options.menuItemPadding - 菜单项内边距配置
+ * @param {Object} options.fontSize - 字体大小配置
+ */
+export function useNavigationConfig(options = {}) {
+  // 解构配置选项,提供默认值
+  const {
+    zhWidth = '200px',
+    enWidth = '260px',
+    menu1Width = { zh: '104px', en: '140px' },
+    menu2Width = { zh: '110px', en: '130px' },
+    menuItemPadding = { zh: '16px', en: '20px' },
+    fontSize = { zh: '14px', en: '13px' },
+    subMenuPadding = { zh: '8px', en: '12px' },
+  } = options;
+
+  // 根据当前语言计算导航栏总宽度
+  const navigationWidth = computed(() => {
+    return getCurrentLanguage() === 'en-US' ? enWidth : zhWidth;
+  });
+
+  // 根据当前语言计算主菜单宽度 (menu-1)
+  const menu1WidthValue = computed(() => {
+    return getCurrentLanguage() === 'en-US' ? menu1Width.en : menu1Width.zh;
+  });
+
+  // 根据当前语言计算子菜单宽度 (menu-2)
+  const menu2WidthValue = computed(() => {
+    return getCurrentLanguage() === 'en-US' ? menu2Width.en : menu2Width.zh;
+  });
+
+  // 根据当前语言计算菜单项内边距
+  const menuItemPaddingValue = computed(() => {
+    return getCurrentLanguage() === 'en-US' ? menuItemPadding.en : menuItemPadding.zh;
+  });
+
+  // 根据当前语言计算子菜单内边距
+  const subMenuPaddingValue = computed(() => {
+    return getCurrentLanguage() === 'en-US' ? subMenuPadding.en : subMenuPadding.zh;
+  });
+
+  // 根据当前语言计算字体大小
+  const fontSizeValue = computed(() => {
+    return getCurrentLanguage() === 'en-US' ? fontSize.en : fontSize.zh;
+  });
+
+  // 菜单项样式配置
+  const menuItemStyle = computed(() => {
+    return {
+      padding: `0 ${menuItemPaddingValue.value}`,
+      fontSize: fontSizeValue.value,
+      minWidth: getCurrentLanguage() === 'en-US' ? '120px' : '80px',
+    };
+  });
+
+  // 子菜单项样式配置
+  const subMenuItemStyle = computed(() => {
+    return {
+      padding: `0 ${subMenuPaddingValue.value}`,
+      fontSize: fontSizeValue.value,
+      minWidth: getCurrentLanguage() === 'en-US' ? '140px' : '100px',
+    };
+  });
+
+  // 导航容器样式配置
+  const navigationContainerStyle = computed(() => {
+    return {
+      width: navigationWidth.value,
+      minWidth: navigationWidth.value,
+      maxWidth: getCurrentLanguage() === 'en-US' ? '320px' : '240px',
+    };
+  });
+
+  // 响应式导航配置(适用于不同屏幕尺寸)
+  const responsiveNavigationConfig = computed(() => {
+    const baseWidth = parseInt(navigationWidth.value);
+
+    return {
+      xs: `${Math.max(baseWidth - 40, 160)}px`, // 小屏幕,最小160px
+      sm: `${Math.max(baseWidth - 20, 180)}px`, // 中小屏幕,最小180px
+      md: `${baseWidth}px`, // 中等屏幕
+      lg: `${baseWidth + 20}px`, // 大屏幕
+      xl: `${baseWidth + 40}px`, // 超大屏幕
+    };
+  });
+
+  // 菜单文本截断配置
+  const textTruncateConfig = computed(() => {
+    return {
+      maxWidth: getCurrentLanguage() === 'en-US' ? '180px' : '120px',
+      overflow: 'hidden',
+      textOverflow: 'ellipsis',
+      whiteSpace: 'nowrap',
+    };
+  });
+
+  return {
+    // 基础配置
+    navigationWidth,
+    menu1WidthValue,
+    menu2WidthValue,
+    menuItemPaddingValue,
+    subMenuPaddingValue,
+    fontSizeValue,
+
+    // 样式配置
+    menuItemStyle,
+    subMenuItemStyle,
+    navigationContainerStyle,
+    textTruncateConfig,
+
+    // 响应式配置
+    responsiveNavigationConfig,
+
+    // 便捷方法
+    getCurrentLanguage,
+  };
+}

+ 211 - 82
src/locales/en-US/index.json

@@ -24,6 +24,11 @@
     "collapse": "Collapse",
     "allow": "Allow",
     "forbid": "Forbid",
+    "submitting": "Submitting...",
+    "memo": "Memo",
+    "tip": "Tip",
+    "status": "Status",
+    "detail": "Detail",
     "select": "Please select",
     "previous": "Previous",
     "uploadImage": "Upload Image",
@@ -74,39 +79,65 @@
     "endTime": "End Time",
     "startDate": "Start Date",
     "endDate": "End Date",
+    "to": "to",
     "exportData": "Export Data",
     "exporting": "Exporting...",
-    "to": "to",
     "and": "and",
     "or": "or",
     "language": "Language",
     "chinese": "中文",
-    "english": "English"
+    "english": "English",
+    "unknown": "Unknown",
+    "fetchDataFailed": "Failed to fetch data",
+    "createTime": "Create Time"
   },
   "menu": {
     "dashboard": "Dashboard",
-    "goods": "Goods Management",
-    "order": "Order Management",
-    "user": "User Management",
-    "system": "System Management",
+    "goods": "Goods",
+    "order": "Order",
+    "user": "User",
+    "system": "System",
     "settings": "Settings",
     "goodsList": "Goods List",
     "goodsAdd": "Add Goods",
     "goodsEdit": "Edit Goods",
-    "goodsCategory": "Goods Category",
-    "goodsBrand": "Goods Brand",
-    "orderList": "Order List",
-    "orderDetail": "Order Detail",
-    "orderRefund": "Refund Management",
-    "userList": "User List",
-    "userRole": "Role Management",
-    "userPermission": "Permission Management",
-    "systemConfig": "System Configuration",
-    "systemLog": "System Log",
-    "systemBackup": "Data Backup",
+    "goodsCategory": "Category",
+    "goodsBrand": "Brand",
+    "orderList": "Orders",
+    "orderDetail": "Detail",
+    "orderRefund": "Refund",
+    "userList": "Users",
+    "userRole": "Roles",
+    "userPermission": "Permissions",
+    "systemConfig": "Config",
+    "systemLog": "Logs",
+    "systemBackup": "Backup",
     "profile": "Profile",
-    "account": "Account Settings",
-    "security": "Security Settings"
+    "account": "Account",
+    "security": "Security",
+    "shopManagement": "Shop",
+    "dataOverview": "Overview",
+    "goodsLibrary": "Library",
+    "orderManagement": "Orders",
+    "marketing": "Marketing",
+    "groupBuying": "Group Buy",
+    "content": "Content",
+    "messagePush": "Push",
+    "sms": "SMS",
+    "bannerAd": "Banner",
+    "data": "Data",
+    "dataReport": "Reports",
+    "finance": "Finance",
+    "recharge": "Recharge",
+    "withdraw": "Withdraw",
+    "commission": "Commission",
+    "financeReport": "Reports",
+    "config": "Config",
+    "menuPermission": "Menu Auth",
+    "staffManagement": "Staff",
+    "roleManagement": "Roles",
+    "carouselBanner": "Carousel",
+    "paymentConfig": "Payment"
   },
   "form": {
     "name": "Name",
@@ -172,6 +203,10 @@
     "selectLoginPermission": "Please select login permission",
     "selectOrderPermission": "Please select order permission",
     "selectWithdrawPermission": "Please select withdraw permission",
+    "inputUserPhone": "Please input user phone",
+    "inputUserNickname": "Please input user nickname",
+    "inputNewPassword": "Please input new password",
+    "inputConfirmPassword": "Please confirm password",
     "inputCategoryName": "Please input category name",
     "inputSort": "Please input sort order",
     "inputGoodsName": "Please input goods name (max 100 characters)",
@@ -206,7 +241,20 @@
     "goodsSupplierRequired": "Please input goods supplier",
     "mainImageRequired": "Please upload main image",
     "carouselImagesRequired": "Please upload carousel images",
-    "detailImagesRequired": "Please upload detail images"
+    "detailImagesRequired": "Please upload detail images",
+    "inputUserPhone": "Please enter user phone",
+    "inputUserNickname": "Please enter user nickname",
+    "uploadUserAvatar": "Upload user avatar",
+    "inputPaymentBank": "Please enter payment bank",
+    "inputAccountName": "Please enter account name",
+    "inputPaymentAccount": "Please enter payment account",
+    "inputNewPassword": "Please enter new password",
+    "inputConfirmPassword": "Please enter new password again",
+    "passwordRequired": "Please enter new password",
+    "passwordLength": "Password length should be 6-20 characters",
+    "confirmPasswordRequired": "Please enter new password again",
+    "passwordMismatch": "The two passwords do not match",
+    "inputMemo": "Please enter memo (max 100 characters)"
   },
   "message": {
     "loginSuccess": "Login successful",
@@ -235,6 +283,8 @@
     "confirmBatchDelete": "Are you sure you want to delete selected records?",
     "confirmSave": "Are you sure you want to save?",
     "confirmSubmit": "Are you sure you want to submit?",
+    "deleteConfirm": "This operation will delete, continue?",
+    "passwordUpdateSuccess": "Password updated successfully",
     "confirmReset": "Are you sure you want to reset?",
     "confirmBatchOnSale": "This operation will batch put goods on sale, continue?",
     "confirmBatchOffSale": "This operation will batch take goods off sale, continue?",
@@ -244,10 +294,8 @@
     "uploadError": "Upload failed",
     "unsupportedFormat": "{extension} format is not supported, please select {formats} format images",
     "fileSizeExceeded": "Image size cannot exceed {size}MB",
-    "loading": "Loading...",
     "saving": "Saving...",
     "deleting": "Deleting...",
-    "uploading": "Uploading...",
     "processing": "Processing...",
     "noData": "No data",
     "noSearchResult": "No search results found",
@@ -375,6 +423,80 @@
       "decrease": "Decrease",
       "noChange": "No Change"
     },
+    "user": {
+      "userId": "User ID",
+      "userName": "Username",
+      "userPhone": "Phone Number",
+      "userNickname": "Nickname",
+      "userLevel": "User Level",
+      "userAvatar": "Avatar",
+      "userAccount": "User Account",
+      "inviteFriends": "Invited Friends",
+      "teamMembers": "Team Members",
+      "successOrders": "Success Orders",
+      "accountBalance": "Account Balance",
+      "commissionBalance": "Commission Balance",
+      "loginPermission": "Login Permission",
+      "orderPermission": "Order Permission",
+      "withdrawPermission": "Withdraw Permission",
+      "registrationTime": "Registration Time",
+      "changePassword": "Change Password",
+      "userDetail": "User Detail",
+      "addUser": "Add User",
+      "editUser": "Edit User",
+      "editProfile": "Edit Profile",
+      "basicInfo": "Basic Information",
+      "avatar": "Avatar",
+      "noLevel": "No Level",
+      "paymentBank": "Payment Bank",
+      "accountName": "Account Name",
+      "paymentAccount": "Payment Account",
+      "newPassword": "New Password",
+      "confirmPassword": "Confirm Password",
+      "login": "Login",
+      "order": "Order",
+      "withdraw": "Withdraw",
+      "userList": "User List",
+      "statisticsInfo": "Statistics Information",
+      "availableGroups": "Available Groups",
+      "totalCommission": "Total Commission Amount",
+      "settledCommission": "Settled Commission Balance",
+      "totalRecharge": "Total Recharge Amount",
+      "last7DaysCommission": "Last 7 Days Commission",
+      "pendingCommission": "Pending Commission Balance",
+      "totalWithdraw": "Total Withdraw Amount",
+      "orderRecords": "Order Records",
+      "subordinateUsers": "Subordinate Users",
+      "commissionRecords": "Commission Records",
+      "commissionId": "Commission ID",
+      "commissionType": "Commission Type",
+      "commissionDescription": "Commission Description",
+      "commissionAmount": "Commission Amount",
+      "commissionStatus": "Commission Status",
+      "commissionTime": "Commission Time",
+      "rechargeRecords": "Recharge Records",
+      "rechargeOrderNo": "Recharge Order No.",
+      "rechargeChannel": "Recharge Channel",
+      "currency": "Currency",
+      "amount": "Amount",
+      "successTime": "Success Time",
+      "withdrawRecords": "Withdraw Records",
+      "withdrawOrderNo": "Withdraw Order No.",
+      "withdrawType": "Withdraw Type",
+      "withdrawChannel": "Withdraw Channel",
+      "withdrawAmount": "Withdraw Amount",
+      "shippingAddresses": "Shipping Addresses",
+      "recipient": "Recipient",
+      "detailedAddress": "Detailed Address",
+      "isDefault": "Is Default",
+      "defaultAddress": "Default Address",
+      "normalAddress": "Normal Address",
+      "userPhone": "User Phone",
+      "userNickname": "User Nickname",
+      "userAvatar": "User Avatar",
+      "newPassword": "New Password",
+      "confirmPassword": "Confirm Password"
+    },
     "goods": {
       "goodsList": "Goods List",
       "goodsId": "Goods ID",
@@ -482,6 +604,7 @@
       "orderList": "Order List",
       "orderDetail": "Order Detail",
       "orderNo": "Order No.",
+      "orderNumber": "Order Number",
       "orderStatus": "Order Status",
       "orderAmount": "Order Amount",
       "orderTime": "Order Time",
@@ -504,6 +627,17 @@
       "goodsName": "Goods Name",
       "goodsPrice": "Goods Price",
       "goodsQuantity": "Quantity",
+      "quantity": "Quantity",
+      "paymentAmount": "Payment Amount",
+      "paymentStatus": "Payment Status",
+      "paid": "Paid",
+      "pendingPayment": "Pending Payment",
+      "groupStatus": "Group Status",
+      "refundStatus": "Refund Status",
+      "logistics": "Logistics",
+      "courier": "Courier",
+      "notShipped": "Not Shipped",
+      "payTime": "Pay Time",
       "goodsTotal": "Subtotal",
       "subtotal": "Subtotal",
       "shippingFee": "Shipping Fee",
@@ -532,7 +666,9 @@
       "refundOrder": "Refund Order",
       "viewDetail": "View Detail",
       "printOrder": "Print Order",
-      "exportOrder": "Export Order"
+      "exportOrder": "Export Order",
+      "exportOrders": "Export Orders",
+      "exportDeliveryList": "Export Delivery List"
     },
     "system": {
       "systemManagement": "System Management",
@@ -597,64 +733,57 @@
       "backup": "Backup",
       "restore": "Restore"
     },
-    "user": {
-      "userManagement": "User Management",
-      "userList": "User List",
-      "userDetail": "User Detail",
-      "addUser": "Add User",
-      "editUser": "Edit User",
-      "deleteUser": "Delete User",
-      "userId": "User ID",
-      "userName": "User Name",
-      "userAccount": "User Account",
-      "userNickname": "Nickname",
-      "userPhone": "Phone Number",
-      "userEmail": "Email Address",
-      "userAvatar": "Avatar",
-      "userGender": "Gender",
-      "userBirthday": "Birthday",
-      "userAddress": "Address",
-      "male": "Male",
-      "female": "Female",
-      "unknown": "Unknown",
-      "userStatus": "User Status",
-      "active": "Active",
-      "inactive": "Inactive",
-      "frozen": "Frozen",
-      "userLevel": "User Level",
-      "levelManagement": "Level Management",
-      "levelName": "Level Name",
-      "levelDiscount": "Level Discount",
-      "levelCondition": "Upgrade Condition",
-      "userTag": "User Tag",
-      "tagManagement": "Tag Management",
-      "tagName": "Tag Name",
-      "tagColor": "Tag Color",
-      "tagDescription": "Tag Description",
-      "totalUsers": "Total Users",
-      "activeUsers": "Active Users",
-      "newUsers": "New Users",
-      "registrationTime": "Registration Time",
-      "lastLoginTime": "Last Login",
-      "loginCount": "Login Count",
-      "userBehavior": "User Behavior",
-      "orderCount": "Order Count",
-      "totalConsumption": "Total Consumption",
-      "averageOrderValue": "Average Order Value",
-      "loginPermission": "Login Permission",
-      "orderPermission": "Order Permission",
-      "withdrawPermission": "Withdraw Permission",
-      "viewProfile": "View Profile",
-      "editProfile": "Edit Profile",
-      "resetPassword": "Reset Password",
-      "enableUser": "Enable User",
-      "disableUser": "Disable User",
-      "freezeUser": "Freeze User",
-      "phoneRequired": "Please enter phone number",
-      "emailRequired": "Please enter email address",
-      "nameRequired": "Please enter user name",
-      "invalidPhone": "Invalid phone number format",
-      "invalidEmail": "Invalid email format"
+    "marketing": {
+      "pending": "Pending",
+      "ongoing": "Ongoing",
+      "ended": "Ended",
+      "groupActivityList": "Group Activity List",
+      "addActivity": "Add Activity",
+      "activityId": "Activity ID",
+      "activityTitle": "Activity Title",
+      "activityStatus": "Activity Status",
+      "groupConfig": "Group Config",
+      "defaultGroupSize": "Default Group Size",
+      "personGroup": " Person Group",
+      "countdownEnd": "Countdown End",
+      "hours": " Hours",
+      "startTime": "Start Time",
+      "endTime": "End Time",
+      "setGoods": "Set Goods",
+      "createGroupActivity": "Create Group Activity",
+      "editGroupActivity": "Edit Group Activity",
+      "enterActivityTitle": "Enter activity title",
+      "enterActivityId": "Enter activity ID",
+      "activityName": "Activity Name",
+      "enterActivityName": "Enter activity name",
+      "selectStartTime": "Select start time",
+      "selectEndTime": "Select end time",
+      "selectGroupSize": "Select group size",
+      "fivePersonGroup": "5 Person Group",
+      "tenPersonGroup": "10 Person Group",
+      "twentyPersonGroup": "20 Person Group",
+      "selectCountdownTime": "Select countdown time",
+      "twelveHours": "12 Hours",
+      "twentyFourHours": "24 Hours",
+      "fortyEightHours": "48 Hours",
+      "seventyTwoHours": "72 Hours",
+      "oneWeek": "168 Hours",
+      "countdownTip": "How long after group formation to end the group buying activity",
+      "activityNameRequired": "Please enter activity name",
+      "startTimeRequired": "Please select start time",
+      "endTimeRequired": "Please select end time",
+      "groupSizeRequired": "Please select group size",
+      "countdownTimeRequired": "Please select countdown time"
+    },
+    "order": {
+      "importDeliveryTip": "Import delivery list, system will automatically process delivery information",
+      "importDeliveryFile": "Import Delivery File",
+      "reSelectFile": "Re-select File",
+      "batchDispatch": "Batch Dispatch",
+      "pleaseSelectFile": "Please select delivery file first",
+      "batchDispatchSuccess": "Batch dispatch successful",
+      "batchDispatchFailed": "Batch dispatch failed",
+      "partialDispatchFailed": "Partial dispatch failed, error records downloaded"
     }
   }
-}
+}

+ 64 - 0
src/locales/navigation.js

@@ -0,0 +1,64 @@
+/**
+ * 导航菜单国际化映射表
+ * 将后端返回的 composingKey 映射到前端翻译键
+ */
+export const navigationMap = {
+  // 一级菜单
+  'shop.admin': 'menu.shopManagement',
+
+  // 二级菜单
+  'shop.admin.dashboard': 'menu.dashboard',
+  'shop.admin.goods': 'menu.goods',
+  'shop.admin.order': 'menu.order',
+  'shop.admin.user': 'menu.user',
+  'shop.admin.marketing': 'menu.marketing',
+  'shop.admin.content': 'menu.content',
+  'shop.admin.data': 'menu.data',
+  'shop.admin.finance': 'menu.finance',
+  'admin.config': 'menu.config',
+
+  // 三级菜单 - 首页(数据概览页面使用相同的key,但在页面级别区分)
+  // 'shop.admin.dashboard': 'menu.dataOverview', // 注释掉重复的映射
+
+  // 三级菜单 - 商品
+  'shop.admin.goods.goods': 'menu.goodsLibrary',
+  'shop.admin.goods.category': 'menu.goodsCategory',
+
+  // 三级菜单 - 订单
+  'shop.admin.order.list': 'menu.orderManagement',
+
+  // 三级菜单 - 用户
+  'shop.admin.user.list': 'menu.userList',
+
+  // 三级菜单 - 营销
+  'shop.admin.marketing.group': 'menu.groupBuying',
+
+  // 三级菜单 - 内容
+  'shop.admin.content.notification': 'menu.messagePush',
+  'shop.admin.content.sms': 'menu.sms',
+  'shop.admin.content.banner': 'menu.bannerAd',
+
+  // 三级菜单 - 数据
+  'shop.admin.data.report': 'menu.dataReport',
+
+  // 三级菜单 - 财务
+  'shop.admin.finance.recharge': 'menu.recharge',
+  'shop.admin.finance.withdraw': 'menu.withdraw',
+  'shop.admin.finance.commission': 'menu.commission',
+  'shop.admin.finance.report': 'menu.financeReport',
+
+  // 三级菜单 - 配置
+  'admin.auth.access': 'menu.menuPermission',
+  'admin.auth.admin': 'menu.staffManagement',
+  'admin.auth.role': 'menu.roleManagement',
+  'admin.banner': 'menu.carouselBanner',
+  'admin.payment': 'menu.paymentConfig',
+};
+
+/**
+ * 特殊处理的菜单项
+ * 某些菜单可能需要特殊的翻译逻辑
+ */
+export const specialMenuItems = {
+  // 可以在这里添加需要特殊处理的菜单项
+};

+ 197 - 67
src/locales/zh-CN/index.json

@@ -37,6 +37,10 @@
     "disable": "禁用",
     "enabled": "已启用",
     "disabled": "已禁用",
+    "submitting": "提交中...",
+    "memo": "备注",
+    "tip": "提示",
+    "status": "状态",
     "online": "上架",
     "offline": "下架",
     "active": "激活",
@@ -81,13 +85,17 @@
     "or": "或",
     "language": "语言",
     "chinese": "中文",
-    "english": "English"
+    "english": "English",
+    "detail": "详情",
+    "unknown": "未知",
+    "fetchDataFailed": "获取数据失败",
+    "createTime": "创建时间"
   },
   "menu": {
-    "dashboard": "仪表盘",
-    "goods": "商品管理",
-    "order": "订单管理",
-    "user": "用户管理",
+    "dashboard": "首页",
+    "goods": "商品",
+    "order": "订单",
+    "user": "用户",
     "system": "系统管理",
     "settings": "设置",
     "goodsList": "商品列表",
@@ -106,7 +114,30 @@
     "systemBackup": "数据备份",
     "profile": "个人资料",
     "account": "账户设置",
-    "security": "安全设置"
+    "security": "安全设置",
+    "shopManagement": "商城管理",
+    "dataOverview": "数据概览",
+    "goodsLibrary": "商品库",
+    "orderManagement": "订单管理",
+    "marketing": "营销",
+    "groupBuying": "拼团",
+    "content": "内容",
+    "messagePush": "消息推送",
+    "sms": "短信",
+    "bannerAd": "广告位",
+    "data": "数据",
+    "dataReport": "数据报表",
+    "finance": "财务",
+    "recharge": "充值",
+    "withdraw": "提款",
+    "commission": "佣金",
+    "financeReport": "报表",
+    "config": "配置",
+    "menuPermission": "菜单权限",
+    "staffManagement": "人员管理",
+    "roleManagement": "角色管理",
+    "carouselBanner": "轮播图",
+    "paymentConfig": "支付配置"
   },
   "form": {
     "name": "名称",
@@ -172,6 +203,10 @@
     "selectLoginPermission": "请选择登录权限",
     "selectOrderPermission": "请选择下单权限",
     "selectWithdrawPermission": "请选择提现权限",
+    "inputUserPhone": "请输入用户手机号",
+    "inputUserNickname": "请输入用户昵称",
+    "inputNewPassword": "请输入新密码",
+    "inputConfirmPassword": "请确认密码",
     "inputCategoryName": "请输入分类名称",
     "inputSort": "请填写排序",
     "inputGoodsName": "请输入商品名称(限100字符)",
@@ -206,7 +241,20 @@
     "goodsSupplierRequired": "请输入商品供应商",
     "mainImageRequired": "请上传商品主图",
     "carouselImagesRequired": "请上传轮播图",
-    "detailImagesRequired": "请上传商品详情图"
+    "detailImagesRequired": "请上传商品详情图",
+    "inputUserPhone": "请输入用户手机",
+    "inputUserNickname": "请输入用户昵称",
+    "uploadUserAvatar": "上传用户头像",
+    "inputPaymentBank": "请输入收款银行",
+    "inputAccountName": "请输入账户名称",
+    "inputPaymentAccount": "请输入收款账户",
+    "inputNewPassword": "请输入新密码",
+    "inputConfirmPassword": "请再次输入新密码",
+    "passwordRequired": "请输入新密码",
+    "passwordLength": "密码长度为6-20位",
+    "confirmPasswordRequired": "请再次输入新密码",
+    "passwordMismatch": "两次输入的密码不一致",
+    "inputMemo": "请输入备注(限100字)"
   },
   "message": {
     "loginSuccess": "登录成功",
@@ -235,6 +283,8 @@
     "confirmBatchDelete": "确定要删除选中的记录吗?",
     "confirmSave": "确定要保存吗?",
     "confirmSubmit": "确定要提交吗?",
+    "deleteConfirm": "此操作将删除, 是否继续?",
+    "passwordUpdateSuccess": "密码修改成功",
     "confirmReset": "确定要重置吗?",
     "confirmBatchOnSale": "此操作将批量上架商品,是否继续?",
     "confirmBatchOffSale": "此操作将批量下架商品,是否继续?",
@@ -375,6 +425,80 @@
       "decrease": "下降",
       "noChange": "持平"
     },
+    "user": {
+      "userId": "用户ID",
+      "userName": "用户名",
+      "userPhone": "手机号码",
+      "userNickname": "用户昵称",
+      "userLevel": "用户等级",
+      "userAvatar": "用户头像",
+      "userAccount": "用户账号",
+      "inviteFriends": "邀请好友数",
+      "teamMembers": "团队人数",
+      "successOrders": "成功订单",
+      "accountBalance": "账户余额",
+      "commissionBalance": "佣金余额",
+      "loginPermission": "登录权限",
+      "orderPermission": "下单权限",
+      "withdrawPermission": "提现权限",
+      "registrationTime": "注册时间",
+      "changePassword": "修改密码",
+      "userDetail": "用户详情",
+      "addUser": "新建用户",
+      "editUser": "编辑用户",
+      "editProfile": "编辑资料",
+      "basicInfo": "基本信息",
+      "avatar": "头像",
+      "noLevel": "暂无等级",
+      "paymentBank": "收款银行",
+      "accountName": "账户名称",
+      "paymentAccount": "收款账户",
+      "newPassword": "新密码",
+      "confirmPassword": "确认密码",
+      "login": "登录",
+      "order": "下单",
+      "withdraw": "提现",
+      "userList": "用户列表",
+      "statisticsInfo": "统计信息",
+      "availableGroups": "可参团次数",
+      "totalCommission": "全部佣金总金额",
+      "settledCommission": "已结算佣金余额",
+      "totalRecharge": "充值总金额",
+      "last7DaysCommission": "近7天佣金余额",
+      "pendingCommission": "待结算佣金余额",
+      "totalWithdraw": "提现总金额",
+      "orderRecords": "订单记录",
+      "subordinateUsers": "下线用户",
+      "commissionRecords": "佣金记录",
+      "commissionId": "佣金ID",
+      "commissionType": "佣金类型",
+      "commissionDescription": "佣金说明",
+      "commissionAmount": "佣金金额",
+      "commissionStatus": "佣金状态",
+      "commissionTime": "佣金发放时间",
+      "rechargeRecords": "充值记录",
+      "rechargeOrderNo": "充值订单号",
+      "rechargeChannel": "充值渠道",
+      "currency": "币种",
+      "amount": "金额",
+      "successTime": "成功时间",
+      "withdrawRecords": "提现记录",
+      "withdrawOrderNo": "提款订单号",
+      "withdrawType": "提款类型",
+      "withdrawChannel": "提款渠道",
+      "withdrawAmount": "提款金额",
+      "shippingAddresses": "收货地址",
+      "recipient": "收件人",
+      "detailedAddress": "详细地址",
+      "isDefault": "是否默认",
+      "defaultAddress": "默认地址",
+      "normalAddress": "普通地址",
+      "userPhone": "用户手机",
+      "userNickname": "用户昵称",
+      "userAvatar": "用户头像",
+      "newPassword": "新密码",
+      "confirmPassword": "确认密码"
+    },
     "goods": {
       "goodsList": "商品列表",
       "goodsId": "商品ID",
@@ -452,7 +576,7 @@
       "uploadMainImage": "上传商品主图",
       "uploadSliderImage": "上传轮播图",
       "uploadDetailImage": "上传详情图",
-      "specification": "商品规格",
+      "specification": "规格",
       "specType": "规格类型",
       "singleSpec": "单规格",
       "multiSpec": "多规格",
@@ -486,6 +610,7 @@
       "orderList": "订单列表",
       "orderDetail": "订单详情",
       "orderNo": "订单号",
+      "orderNumber": "订单编号",
       "orderStatus": "订单状态",
       "orderAmount": "订单金额",
       "orderTime": "下单时间",
@@ -508,6 +633,16 @@
       "goodsName": "商品名称",
       "goodsPrice": "商品价格",
       "goodsQuantity": "商品数量",
+      "quantity": "数量",
+      "paymentAmount": "付款金额",
+      "paid": "已付款",
+      "pendingPayment": "待付款",
+      "groupStatus": "拼团状态",
+      "refundStatus": "退款状态",
+      "logistics": "物流信息",
+      "courier": "快递公司",
+      "notShipped": "未发货",
+      "payTime": "付款时间",
       "goodsTotal": "商品小计",
       "subtotal": "商品小计",
       "shippingFee": "运费",
@@ -536,7 +671,9 @@
       "refundOrder": "退款",
       "viewDetail": "查看详情",
       "printOrder": "打印订单",
-      "exportOrder": "导出订单"
+      "exportOrder": "导出订单",
+      "exportOrders": "订单导出",
+      "exportDeliveryList": "导出发货单"
     },
     "system": {
       "systemManagement": "系统管理",
@@ -601,64 +738,57 @@
       "backup": "备份",
       "restore": "恢复"
     },
-    "user": {
-      "userManagement": "用户管理",
-      "userList": "用户列表",
-      "userDetail": "用户详情",
-      "addUser": "添加用户",
-      "editUser": "编辑用户",
-      "deleteUser": "删除用户",
-      "userId": "用户ID",
-      "userName": "用户姓名",
-      "userAccount": "用户账号",
-      "userNickname": "用户昵称",
-      "userPhone": "手机号码",
-      "userEmail": "邮箱地址",
-      "userAvatar": "用户头像",
-      "userGender": "性别",
-      "userBirthday": "生日",
-      "userAddress": "地址",
-      "male": "男",
-      "female": "女",
-      "unknown": "未知",
-      "userStatus": "用户状态",
-      "active": "正常",
-      "inactive": "禁用",
-      "frozen": "冻结",
-      "userLevel": "用户等级",
-      "levelManagement": "等级管理",
-      "levelName": "等级名称",
-      "levelDiscount": "等级折扣",
-      "levelCondition": "升级条件",
-      "userTag": "用户标签",
-      "tagManagement": "标签管理",
-      "tagName": "标签名称",
-      "tagColor": "标签颜色",
-      "tagDescription": "标签描述",
-      "totalUsers": "总用户数",
-      "activeUsers": "活跃用户",
-      "newUsers": "新增用户",
-      "registrationTime": "注册时间",
-      "lastLoginTime": "最后登录",
-      "loginCount": "登录次数",
-      "userBehavior": "用户行为",
-      "orderCount": "订单数量",
-      "totalConsumption": "消费总额",
-      "averageOrderValue": "客单价",
-      "loginPermission": "登录权限",
-      "orderPermission": "下单权限",
-      "withdrawPermission": "提现权限",
-      "viewProfile": "查看资料",
-      "editProfile": "编辑资料",
-      "resetPassword": "重置密码",
-      "enableUser": "启用用户",
-      "disableUser": "禁用用户",
-      "freezeUser": "冻结用户",
-      "phoneRequired": "请输入手机号码",
-      "emailRequired": "请输入邮箱地址",
-      "nameRequired": "请输入用户姓名",
-      "invalidPhone": "手机号码格式不正确",
-      "invalidEmail": "邮箱格式不正确"
+    "marketing": {
+      "pending": "待开始",
+      "ongoing": "进行中",
+      "ended": "已结束",
+      "groupActivityList": "拼团活动列表",
+      "addActivity": "添加活动",
+      "activityId": "活动编号",
+      "activityTitle": "活动标题",
+      "activityStatus": "活动状态",
+      "groupConfig": "拼团配置",
+      "defaultGroupSize": "成团默认人数",
+      "personGroup": "人团",
+      "countdownEnd": "开团倒计时结束",
+      "hours": "小时",
+      "startTime": "活动开始时间",
+      "endTime": "活动结束时间",
+      "setGoods": "设置商品",
+      "createGroupActivity": "新建拼团活动",
+      "editGroupActivity": "编辑拼团活动",
+      "enterActivityTitle": "请输入活动标题",
+      "enterActivityId": "请输入活动编号",
+      "activityName": "活动名称",
+      "enterActivityName": "请填写活动名称",
+      "selectStartTime": "请选择开始时间",
+      "selectEndTime": "请选择结束时间",
+      "selectGroupSize": "请选择成团默认人数",
+      "fivePersonGroup": "5人团",
+      "tenPersonGroup": "10人团",
+      "twentyPersonGroup": "20人团",
+      "selectCountdownTime": "请选择开团倒计时结束",
+      "twelveHours": "12小时",
+      "twentyFourHours": "24小时",
+      "fortyEightHours": "48小时",
+      "seventyTwoHours": "72小时",
+      "oneWeek": "168小时",
+      "countdownTip": "开团后倒计时多久结束拼团活动",
+      "activityNameRequired": "请填写活动名称",
+      "startTimeRequired": "请选择开始时间",
+      "endTimeRequired": "请选择结束时间",
+      "groupSizeRequired": "请选择成团默认人数",
+      "countdownTimeRequired": "请选择开团倒计时结束"
+    },
+    "order": {
+      "importDeliveryTip": "导入发货单,系统将自动处理发货信息",
+      "importDeliveryFile": "导入发货单",
+      "reSelectFile": "重新选择文件",
+      "batchDispatch": "批量发货",
+      "pleaseSelectFile": "请先选择发货单文件",
+      "batchDispatchSuccess": "批量发货成功",
+      "batchDispatchFailed": "批量发货失败",
+      "partialDispatchFailed": "部分发货失败,已下载失败记录"
     }
   }
 }

+ 300 - 244
src/sheep/layouts/menu/style/qianniu/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="shop-menu">
-    <el-scrollbar class="menu-1" height="100%">
+    <el-scrollbar class="menu-1" height="100%" :style="{ width: menu1WidthValue }">
       <div class="title my-10px">
         <sa-image :url="appInfo?.logo" size="36" fit="contain"></sa-image>
       </div>
@@ -14,26 +14,14 @@
       <div class="menu-footer">
         <div class="line sa-m-t-10 sa-m-b-10"></div>
         <div class="el-menu-item">
-          <el-tooltip
-            effect="light"
-            content="个人信息"
-            placement="right"
-            :offset="6"
-            popper-class="el-menu-tooltip"
-          >
+          <el-tooltip effect="light" content="个人信息" placement="right" :offset="6" popper-class="el-menu-tooltip">
             <div class="el-menu-tooltip__trigger" @click="toView('admin.profile')">
               <img src="/static/images/shop/avatar.png" class="mr-4px w-40px h-40px rounded-full" />
             </div>
           </el-tooltip>
         </div>
         <div class="el-menu-item sa-m-t-10">
-          <el-tooltip
-            effect="light"
-            content="设置"
-            placement="right"
-            :offset="6"
-            popper-class="el-menu-tooltip"
-          >
+          <el-tooltip effect="light" content="设置" placement="right" :offset="6" popper-class="el-menu-tooltip">
             <div class="el-menu-tooltip__trigger" @click.stop="onOpenSetting">
               <sa-icon icon="sa-setting" size="32" />
             </div>
@@ -41,7 +29,8 @@
         </div>
       </div>
     </el-scrollbar>
-    <el-scrollbar v-if="shop.childrenMenu.length > 0" class="menu-2" height="100%">
+
+    <el-scrollbar v-if="shop.childrenMenu.length > 0" class="menu-2" height="100%" :style="{ width: menu2WidthValue }">
       <el-menu :default-active="route.name" @select="toView">
         <template v-for="menu in shop.childrenMenu" :key="menu">
           <loop-menu :menuItem="menu" :level="0" />
@@ -52,274 +41,341 @@
 </template>
 
 <script setup>
-  import { onMounted, reactive, computed } from 'vue';
-  import sheep from '@/sheep';
-  import { useRouter, useRoute } from 'vue-router';
-  import { useApp, useModal } from '@/sheep/hooks';
-  import MenuItem from './menu-item.vue';
-  import LoopMenu from './loop-menu.vue';
-  import AppSetting from '../../../setting.vue';
-  import { isEmpty } from 'lodash';
-  // import { fromQuery } from 'lodash-contrib';
-
-  // 简单的查询字符串解析函数
-  function fromQuery(queryStr) {
-    if (!queryStr) return {};
-    const params = new URLSearchParams(queryStr);
-    const result = {};
-    for (const [key, value] of params) {
-      result[key] = value;
-    }
-    return result;
+import { onMounted, reactive, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+import sheep from '@/sheep';
+import { useRouter, useRoute } from 'vue-router';
+import { useApp, useModal } from '@/sheep/hooks';
+import MenuItem from './menu-item.vue';
+import LoopMenu from './loop-menu.vue';
+import AppSetting from '../../../setting.vue';
+import { isEmpty } from 'lodash';
+import { translateNavigation } from '@/utils/navigationI18n';
+import { useNavigationConfig } from '@/hooks/useNavigationConfig';
+// import { fromQuery } from 'lodash-contrib';
+
+// 简单的查询字符串解析函数
+function fromQuery(queryStr) {
+  if (!queryStr) return {};
+  const params = new URLSearchParams(queryStr);
+  const result = {};
+  for (const [key, value] of params) {
+    result[key] = value;
   }
+  return result;
+}
 
-  const accountStore = sheep.$store('account');
-  const appStore = sheep.$store('app');
-  const route = useRoute();
-  const router = useRouter();
+const { t, locale } = useI18n();
+const {
+  navigationWidth,
+  menu1WidthValue,
+  menu2WidthValue,
+  navigationContainerStyle,
+  menuItemStyle
+} = useNavigationConfig({
+  menu1Width: { zh: '104px', en: '140px' },  // 主菜单独立宽度
+  menu2Width: { zh: '110px', en: '134px' }   // 子菜单独立宽度
+});
 
-  const { appInfo, appBrowser, appMenu } = useApp();
-  const account = computed(() => accountStore.info);
+const accountStore = sheep.$store('account');
+const appStore = sheep.$store('app');
+const route = useRoute();
+const router = useRouter();
 
-  // 获取商城管理菜单数据
-  const shopApp = computed(() => {
-    return appMenu.value?.find((app) => app.composingKey === 'shop.admin') || { children: [] };
-  });
+const { appInfo, appBrowser, appMenu } = useApp();
+const account = computed(() => accountStore.info);
+
+// 获取商城管理菜单数据(国际化处理)
+const shopApp = computed(() => {
+  const originalApp = appMenu.value?.find((app) => app.composingKey === 'shop.admin') || { children: [] };
 
-  const shop = reactive({
-    name: '',
-    childrenMenu: [],
+  // 对菜单数据进行国际化处理
+  if (originalApp.children && originalApp.children.length > 0) {
+    const translatedChildren = translateNavigation(originalApp.children, t);
+    return {
+      ...originalApp,
+      children: translatedChildren
+    };
+  }
+
+  return originalApp;
+});
+
+const shop = reactive({
+  name: '',
+  childrenMenu: [],
+});
+
+function initShop(data, parent) {
+  data?.forEach((i) => {
+    if (i?.composingKey == route.name) {
+      if (isEmpty(parent)) {
+        shop.name = i.composingKey;
+      } else {
+        shop.name = parent.composingKey;
+        // 对子菜单进行国际化处理
+        shop.childrenMenu = parent.children ? translateNavigation(parent.children, t) : [];
+      }
+    }
+    if (!isEmpty(i?.children)) {
+      initShop(i.children, i);
+    }
   });
+}
+
+function onSelect(composingKey) {
+  // 从原始菜单数据中查找,避免国际化处理后的数据结构问题
+  const originalApp = appMenu.value?.find((app) => app.composingKey === 'shop.admin') || { children: [] };
+  const menu = originalApp.children?.find((m) => m.composingKey == composingKey);
 
-  function initShop(data, parent) {
+  if (!menu || isEmpty(menu.children)) {
+    shop.childrenMenu = [];
+    toView(composingKey);
+  } else {
+    // 对子菜单也进行国际化处理
+    shop.childrenMenu = translateNavigation(menu.children, t);
+  }
+}
+
+async function toView(composingKey) {
+  let item = {};
+  // 使用原始菜单数据进行查找
+  const originalApp = appMenu.value?.find((app) => app.composingKey === 'shop.admin') || { children: [] };
+  if (originalApp.children) {
+    loopType(originalApp.children);
+  }
+  function loopType(data) {
     data?.forEach((i) => {
-      if (i?.composingKey == route.name) {
-        if (isEmpty(parent)) {
-          shop.name = i.composingKey;
-        } else {
-          shop.name = parent.composingKey;
-          shop.childrenMenu = parent.children || [];
-        }
+      if (i.composingKey == composingKey) {
+        item = i;
       }
-      if (!isEmpty(i?.children)) {
-        initShop(i.children, i);
+      if (!isEmpty(i.children)) {
+        loopType(i.children);
       }
     });
   }
-
-  function onSelect(composingKey) {
-    const menu = shopApp.value?.children?.find((m) => m.composingKey == composingKey);
-    if (!menu || isEmpty(menu.children)) {
-      shop.childrenMenu = [];
-      toView(composingKey);
-    } else {
-      shop.childrenMenu = menu.children;
+  // 根据新数据结构处理不同类型的菜单项
+  // type: '1' 菜单, '2' 叶子菜单, '3' 页面, '4' 动作
+  if (item.type == 'modal') {
+    // 保留原有的 modal 类型处理
+    let params = {
+      title: item.label,
+    };
+    if (item.url) params = { ...params, ...fromQuery(item.url) };
+    useModal(composingKey, params);
+  } else if (item.type == '4') {
+    // type: '4' 动作类型,可能需要特殊处理
+    // 这里可以根据具体需求添加动作处理逻辑
+    console.log('执行动作:', item.label, composingKey);
+  } else {
+    // type: '1' 菜单, '2' 叶子菜单, '3' 页面 - 都进行路由跳转
+    if (composingKey != route.name) {
+      router.push({
+        name: composingKey,
+        query: item.url ? fromQuery(item.url) : {},
+      });
+      appStore.menuCollapse(appBrowser.value.isMini);
     }
   }
+}
 
-  async function toView(composingKey) {
-    let item = {};
-    if (shopApp.value?.children) {
-      loopType(shopApp.value.children);
-    }
-    function loopType(data) {
-      data?.forEach((i) => {
-        if (i.composingKey == composingKey) {
-          item = i;
-        }
-        if (!isEmpty(i.children)) {
-          loopType(i.children);
-        }
-      });
-    }
-    // 根据新数据结构处理不同类型的菜单项
-    // type: '1' 菜单, '2' 叶子菜单, '3' 页面, '4' 动作
-    if (item.type == 'modal') {
-      // 保留原有的 modal 类型处理
-      let params = {
-        title: item.label,
-      };
-      if (item.url) params = { ...params, ...fromQuery(item.url) };
-      useModal(composingKey, params);
-    } else if (item.type == '4') {
-      // type: '4' 动作类型,可能需要特殊处理
-      // 这里可以根据具体需求添加动作处理逻辑
-      console.log('执行动作:', item.label, composingKey);
-    } else {
-      // type: '1' 菜单, '2' 叶子菜单, '3' 页面 - 都进行路由跳转
-      if (composingKey != route.name) {
-        router.push({
-          name: composingKey,
-          query: item.url ? fromQuery(item.url) : {},
-        });
-        appStore.menuCollapse(appBrowser.value.isMini);
-      }
-    }
+// 打开设置
+function onOpenSetting() {
+  // 手机端菜单折叠
+  if (navigator.userAgent.includes('Mobile')) {
+    appStore.menuCollapse(true);
   }
 
-  // 打开设置
-  function onOpenSetting() {
-    // 手机端菜单折叠
-    if (navigator.userAgent.includes('Mobile')) {
-      appStore.menuCollapse(true);
-    }
+  useModal(AppSetting, { class: 'app-setting-dialog is-fullscreen', modal: false });
+}
 
-    useModal(AppSetting, { class: 'app-setting-dialog is-fullscreen', modal: false });
+onMounted(() => {
+  // 使用原始菜单数据进行初始化
+  const originalApp = appMenu.value?.find((app) => app.composingKey === 'shop.admin') || { children: [] };
+  if (originalApp.children && originalApp.children.length > 0) {
+    initShop(originalApp.children, {});
   }
-
-  onMounted(() => {
-    if (shopApp.value?.children) {
-      initShop(shopApp.value.children, {});
-    }
-  });
+});
 </script>
 
 <style lang="scss" scoped>
-  .shop-menu {
-    --el-menu-active-color: var(--el-color-primary);
-    --el-menu-text-color: var(--el-text-color-primary);
-    --el-menu-hover-text-color: var(--el-color-primary);
-    --el-menu-bg-color: transparent; //
-    --el-menu-hover-bg-color: transparent; //
-    --el-menu-item-height: 56px;
-    --el-menu-sub-item-height: calc(var(--el-menu-item-height) - 6px);
-    --el-menu-horizontal-sub-item-height: 36px;
-    --el-menu-item-font-size: var(--el-font-size-base);
-    --el-menu-item-hover-fill: var(--el-color-primary-light-9);
-    --el-menu-border-color: var(--el-border-color);
-    --el-menu-base-level-padding: 16px; //
-    --el-menu-level-padding: 16px; //
-    --el-menu-icon-width: 24px;
-    --el-menu-icon-transform-closed: none;
-    --el-menu-icon-transform-open: rotateZ(180deg);
-
-    height: 100%;
+.shop-menu {
+  --el-menu-active-color: var(--el-color-primary);
+  --el-menu-text-color: var(--el-text-color-primary);
+  --el-menu-hover-text-color: var(--el-color-primary);
+  --el-menu-bg-color: transparent; //
+  --el-menu-hover-bg-color: transparent; //
+  --el-menu-item-height: 56px;
+  --el-menu-sub-item-height: calc(var(--el-menu-item-height) - 6px);
+  --el-menu-horizontal-sub-item-height: 36px;
+  --el-menu-item-font-size: var(--el-font-size-base);
+  --el-menu-item-hover-fill: var(--el-color-primary-light-9);
+  --el-menu-border-color: var(--el-border-color);
+  --el-menu-base-level-padding: 16px; //
+  --el-menu-level-padding: 16px; //
+  --el-menu-icon-width: 24px;
+  --el-menu-icon-transform-closed: none;
+  --el-menu-icon-transform-open: rotateZ(180deg);
+
+  height: 100%;
+  display: flex;
+  background: var(--t-bg-color);
+
+  .el-menu {
+    border-right: none;
+  }
+
+  .title {
+    line-height: 64px;
+    padding: 0 12px;
+    font-size: 20px;
+    font-weight: 500;
+    color: var(--sa-subtitle);
     display: flex;
-    background: var(--t-bg-color);
-    .el-menu {
-      border-right: none;
-    }
-    .title {
-      line-height: 64px;
-      padding: 0 12px;
-      font-size: 20px;
-      font-weight: 500;
-      color: var(--sa-subtitle);
-      display: flex;
-      align-items: center;
-      justify-content: center;
-    }
-    .menu-1 {
-      --el-menu-item-height: 48px;
-      --el-menu-text-color: #737182;
-
-      width: 104px;
-      padding: 0 0 0 8px;
-      position: relative;
-
-      .menu-footer {
-        position: absolute;
-        bottom: 0;
-        left: 0;
-        right: 0;
-        padding: 0 8px 12px;
-
-        .line {
-          height: 1px;
-          background: var(--sa-border);
-          margin: 10px 0;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .menu-1 {
+    --el-menu-item-height: 48px;
+    --el-menu-text-color: #737182;
+
+    /* 移除固定宽度,使用动态宽度 */
+    min-width: 104px;
+    padding: 0 0 0 8px;
+    position: relative;
+    flex-shrink: 0;
+
+    .menu-footer {
+      position: absolute;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      padding: 0 8px 12px;
+
+      .line {
+        height: 1px;
+        background: var(--sa-border);
+        margin: 10px 0;
+      }
+
+      .el-menu-item {
+        height: 40px;
+        line-height: 40px;
+        margin-bottom: 4px;
+        border-radius: 8px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
+
+        &:hover {
+          background: var(--el-color-primary-light-9);
+          color: var(--el-color-primary);
         }
 
-        .el-menu-item {
-          height: 40px;
-          line-height: 40px;
-          margin-bottom: 4px;
-          border-radius: 8px;
+        .el-menu-tooltip__trigger {
           display: flex;
           align-items: center;
           justify-content: center;
-          cursor: pointer;
+          width: 100%;
+          height: 100%;
+        }
+      }
+    }
 
-          &:hover {
-            background: var(--el-color-primary-light-9);
-            color: var(--el-color-primary);
-          }
+    :deep() {
+      .el-menu-item {
+        .sa-icon {
+          width: 16px;
+          margin-right: 8px;
+        }
 
-          .el-menu-tooltip__trigger {
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            width: 100%;
-            height: 100%;
-          }
+        &:hover {
+          color: var(--el-color-primary);
         }
-      }
 
-      :deep() {
-        .el-menu-item {
-          .sa-icon {
-            width: 16px;
-            margin-right: 8px;
-          }
-          &:hover {
-            color: var(--el-color-primary);
-          }
-          &.is-active {
-            background: var(--sa-background-assist);
-            border-radius: 16px 0 0 16px;
-            box-shadow: -8px 0 8px rgba(205, 205, 205, 0.02);
-            &::after {
-              content: '';
-              position: absolute;
-              top: -12px;
-              right: 0;
-              width: 12px;
-              height: 12px;
-              background: radial-gradient(
-                circle at 0% 0%,
+        &.is-active {
+          background: var(--sa-background-assist);
+          border-radius: 16px 0 0 16px;
+          box-shadow: -8px 0 8px rgba(205, 205, 205, 0.02);
+
+          &::after {
+            content: '';
+            position: absolute;
+            top: -12px;
+            right: 0;
+            width: 12px;
+            height: 12px;
+            background: radial-gradient(circle at 0% 0%,
                 transparent 12px,
-                var(--sa-background-assist) 0
-              );
-            }
-            &::before {
-              content: '';
-              position: absolute;
-              right: 0;
-              bottom: -12px;
-              width: 12px;
-              height: 12px;
-              background: radial-gradient(
-                circle at 0 100%,
+                var(--sa-background-assist) 0);
+          }
+
+          &::before {
+            content: '';
+            position: absolute;
+            right: 0;
+            bottom: -12px;
+            width: 12px;
+            height: 12px;
+            background: radial-gradient(circle at 0 100%,
                 transparent 12px,
-                var(--sa-background-assist) 0
-              );
-            }
+                var(--sa-background-assist) 0);
           }
         }
       }
     }
-    .menu-2 {
-      --el-menu-text-color: var(--sa-subtitle);
-      --el-menu-item-height: 40px;
-
-      width: 110px;
-      padding: 12px 8px;
-      background: var(--sa-background-assist);
-      border-radius: 8px 0 0;
-      border-right: 1px solid var(--sa-border);
-      :deep() {
-        .el-menu-item {
-          border-radius: 8px;
-          margin-bottom: 4px;
-          &.is-active,
-          &:hover {
-            background: var(--t-bg-hover);
-          }
-        }
-        .sa-icon {
-          display: none !important;
+  }
+
+  .menu-2 {
+    --el-menu-text-color: var(--sa-subtitle);
+    --el-menu-item-height: 40px;
+
+    /* 移除固定宽度,使用动态宽度 */
+    min-width: 110px;
+    padding: 12px 8px;
+    background: var(--sa-background-assist);
+    border-radius: 8px 0 0;
+    border-right: 1px solid var(--sa-border);
+    flex-shrink: 0;
+
+    :deep() {
+      .el-menu-item {
+        border-radius: 8px;
+        margin-bottom: 4px;
+
+        &.is-active,
+        &:hover {
+          background: var(--t-bg-hover);
         }
       }
+
+      .sa-icon {
+        display: none !important;
+      }
+    }
+  }
+
+  /* 响应式设计 */
+  @media (max-width: 768px) {
+    .menu-1 {
+      min-width: 80px;
+    }
+
+    .menu-2 {
+      min-width: 90px;
+    }
+  }
+
+  @media (max-width: 480px) {
+    .menu-1 {
+      min-width: 60px;
+    }
+
+    .menu-2 {
+      min-width: 80px;
     }
   }
+}
 </style>

+ 39 - 12
src/sheep/layouts/menu/style/qianniu/menu-item.vue

@@ -2,22 +2,49 @@
   <el-menu-item :index="menuItem?.composingKey" :style="itemStyle()">
     <sa-icon class="sa-m-r-8" size="16" :icon="menuItem?.logo" />
     <template #title>
-      <span class="sa-line-1">{{ menuItem?.label }}</span>
+      <span class="sa-line-1" :style="textTruncateConfig">{{ getMenuItemText(menuItem) }}</span>
     </template>
   </el-menu-item>
 </template>
 <script setup>
-  const props = defineProps(['menuItem', 'level']);
+import { useI18n } from 'vue-i18n';
+import { getBreadcrumbText } from '@/utils/navigationI18n';
+import { useNavigationConfig } from '@/hooks/useNavigationConfig';
 
-  function itemStyle() {
-    if (props.level) {
-      return {
-        height: '32px',
-        'line-height': '32px',
-        'padding-left': 16 + props.level * 8 + 'px',
-        'font-size': '12px',
-        color: 'var(--sa-font)',
-      };
-    }
+const { t } = useI18n();
+const { textTruncateConfig, subMenuItemStyle } = useNavigationConfig();
+const props = defineProps(['menuItem', 'level']);
+
+// 获取菜单项的显示文本(国际化)
+function getMenuItemText(menuItem) {
+  if (!menuItem) return '';
+
+  // 如果菜单项已经被国际化处理过,直接使用name或label
+  if (menuItem.name !== menuItem.label || !menuItem.composingKey) {
+    return menuItem.name || menuItem.label || '';
+  }
+
+  // 否则使用国际化工具函数获取翻译文本
+  return getBreadcrumbText(menuItem.composingKey, t) || menuItem.label || menuItem.name || '';
+}
+
+function itemStyle() {
+  if (props.level) {
+    // 合并导航配置和原有样式
+    const baseStyle = {
+      height: '32px',
+      'line-height': '32px',
+      'padding-left': 16 + props.level * 8 + 'px',
+      'font-size': '12px',
+      color: 'var(--sa-font)',
+    };
+
+    // 应用子菜单样式配置
+    return {
+      ...baseStyle,
+      ...subMenuItemStyle.value,
+      'padding-left': baseStyle['padding-left'], // 保持原有的层级缩进逻辑
+    };
   }
+}
 </script>

+ 200 - 0
src/utils/navigationI18n.js

@@ -0,0 +1,200 @@
+import { navigationMap, specialMenuItems } from '@/locales/navigation';
+
+/**
+ * 递归处理导航数据,实现国际化
+ * @param {Array} navigationData - 原始导航数据
+ * @param {Function} t - 翻译函数
+ * @returns {Array} 处理后的导航数据
+ */
+export function translateNavigation(navigationData, t) {
+  if (!Array.isArray(navigationData)) {
+    return navigationData;
+  }
+
+  return navigationData.map(item => {
+    const translatedItem = { ...item };
+    
+    // 根据 composingKey 获取翻译键
+    const translationKey = navigationMap[item.composingKey];
+    
+    if (translationKey) {
+      // 使用翻译,如果翻译不存在则保持原文
+      const translatedText = t(translationKey);
+      if (translatedText !== translationKey) {
+        translatedItem.name = translatedText;
+        translatedItem.label = translatedText;
+      }
+    }
+    
+    // 处理特殊菜单项
+    if (specialMenuItems[item.composingKey]) {
+      const specialHandler = specialMenuItems[item.composingKey];
+      if (typeof specialHandler === 'function') {
+        const specialResult = specialHandler(item, t);
+        Object.assign(translatedItem, specialResult);
+      }
+    }
+    
+    // 递归处理子菜单
+    if (item.children && item.children.length > 0) {
+      translatedItem.children = translateNavigation(item.children, t);
+    }
+    
+    return translatedItem;
+  });
+}
+
+/**
+ * 获取面包屑导航的国际化文本
+ * @param {string} composingKey - 组合键
+ * @param {Function} t - 翻译函数
+ * @returns {string} 翻译后的文本
+ */
+export function getBreadcrumbText(composingKey, t) {
+  const translationKey = navigationMap[composingKey];
+  if (translationKey) {
+    const translatedText = t(translationKey);
+    return translatedText !== translationKey ? translatedText : composingKey;
+  }
+  return composingKey;
+}
+
+/**
+ * 根据路由路径查找对应的导航项
+ * @param {Array} navigationData - 导航数据
+ * @param {string} path - 路由路径
+ * @returns {Object|null} 找到的导航项
+ */
+export function findNavigationByPath(navigationData, path) {
+  if (!Array.isArray(navigationData)) {
+    return null;
+  }
+
+  for (const item of navigationData) {
+    if (item.url === path) {
+      return item;
+    }
+    
+    if (item.children && item.children.length > 0) {
+      const found = findNavigationByPath(item.children, path);
+      if (found) {
+        return found;
+      }
+    }
+  }
+  
+  return null;
+}
+
+/**
+ * 根据composingKey查找导航项
+ * @param {Array} navigationData - 导航数据
+ * @param {string} composingKey - 组合键
+ * @returns {Object|null} 找到的导航项
+ */
+export function findNavigationByKey(navigationData, composingKey) {
+  if (!Array.isArray(navigationData)) {
+    return null;
+  }
+
+  for (const item of navigationData) {
+    if (item.composingKey === composingKey) {
+      return item;
+    }
+    
+    if (item.children && item.children.length > 0) {
+      const found = findNavigationByKey(item.children, composingKey);
+      if (found) {
+        return found;
+      }
+    }
+  }
+  
+  return null;
+}
+
+/**
+ * 构建面包屑路径
+ * @param {Array} navigationData - 导航数据
+ * @param {string} currentPath - 当前路径
+ * @param {Function} t - 翻译函数
+ * @returns {Array} 面包屑数组
+ */
+export function buildBreadcrumb(navigationData, currentPath, t) {
+  const breadcrumb = [];
+  
+  function findPath(items, targetPath, path = []) {
+    for (const item of items) {
+      const currentPath = [...path, item];
+      
+      if (item.url === targetPath) {
+        return currentPath;
+      }
+      
+      if (item.children && item.children.length > 0) {
+        const found = findPath(item.children, targetPath, currentPath);
+        if (found) {
+          return found;
+        }
+      }
+    }
+    return null;
+  }
+  
+  const pathItems = findPath(navigationData, currentPath);
+  if (pathItems) {
+    return pathItems.map(item => ({
+      ...item,
+      name: getBreadcrumbText(item.composingKey, t),
+      label: getBreadcrumbText(item.composingKey, t)
+    }));
+  }
+  
+  return breadcrumb;
+}
+
+/**
+ * 获取菜单的层级深度
+ * @param {Array} navigationData - 导航数据
+ * @returns {number} 最大深度
+ */
+export function getNavigationDepth(navigationData) {
+  if (!Array.isArray(navigationData) || navigationData.length === 0) {
+    return 0;
+  }
+  
+  let maxDepth = 1;
+  
+  for (const item of navigationData) {
+    if (item.children && item.children.length > 0) {
+      const childDepth = getNavigationDepth(item.children) + 1;
+      maxDepth = Math.max(maxDepth, childDepth);
+    }
+  }
+  
+  return maxDepth;
+}
+
+/**
+ * 扁平化导航数据
+ * @param {Array} navigationData - 导航数据
+ * @returns {Array} 扁平化后的数组
+ */
+export function flattenNavigation(navigationData) {
+  const result = [];
+  
+  function flatten(items) {
+    for (const item of items) {
+      result.push(item);
+      if (item.children && item.children.length > 0) {
+        flatten(item.children);
+      }
+    }
+  }
+  
+  if (Array.isArray(navigationData)) {
+    flatten(navigationData);
+  }
+  
+  return result;
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно