Procházet zdrojové kódy

feat:完成菜单权限-人员管理等配置模块 以及财务模块的国际化翻译

叶静 před 5 měsíci
rodič
revize
2f85c4cdbe
45 změnil soubory, kde provedl 7877 přidání a 7207 odebrání
  1. 2 2
      .env.development
  2. 880 894
      src/app/admin/views/auth/access/components/sa-access.vue
  3. 207 226
      src/app/admin/views/auth/access/edit.vue
  4. 141 151
      src/app/admin/views/auth/admin/edit.vue
  5. 148 164
      src/app/admin/views/auth/admin/index.vue
  6. 71 72
      src/app/admin/views/auth/admin/password.vue
  7. 131 125
      src/app/admin/views/auth/role/edit.vue
  8. 94 101
      src/app/admin/views/auth/role/index.vue
  9. 104 113
      src/app/admin/views/banner/edit.vue
  10. 105 120
      src/app/admin/views/banner/index.vue
  11. 158 159
      src/app/admin/views/index/profile/index.vue
  12. 87 88
      src/app/admin/views/payment/components/ChannelManage.vue
  13. 151 178
      src/app/admin/views/payment/components/CollectionConfig.vue
  14. 94 103
      src/app/admin/views/payment/components/PayoutConfig.vue
  15. 100 116
      src/app/admin/views/payment/edit.vue
  16. 64 65
      src/app/admin/views/payment/index.vue
  17. 157 158
      src/app/shop/admin/content/banner/index.vue
  18. 156 152
      src/app/shop/admin/content/notification/index.vue
  19. 150 138
      src/app/shop/admin/data/report/index.vue
  20. 208 214
      src/app/shop/admin/finance/commission/detail.vue
  21. 102 113
      src/app/shop/admin/finance/commission/edit.vue
  22. 242 249
      src/app/shop/admin/finance/commission/index.vue
  23. 71 46
      src/app/shop/admin/finance/finance.service.js
  24. 192 162
      src/app/shop/admin/finance/recharge/detail.vue
  25. 68 88
      src/app/shop/admin/finance/recharge/edit.vue
  26. 287 270
      src/app/shop/admin/finance/recharge/index.vue
  27. 274 300
      src/app/shop/admin/finance/report/index.vue
  28. 76 79
      src/app/shop/admin/finance/withdraw/audit.vue
  29. 248 220
      src/app/shop/admin/finance/withdraw/detail.vue
  30. 297 283
      src/app/shop/admin/finance/withdraw/index.vue
  31. 582 662
      src/app/shop/admin/goods/goods/index.vue
  32. 16 29
      src/app/shop/admin/marketing/group/index.vue
  33. 1 1
      src/app/shop/admin/order/order.service.js
  34. 22 30
      src/app/shop/admin/order/order/index.vue
  35. 17 16
      src/app/shop/admin/user/list/index.vue
  36. 156 152
      src/app/shop/admin/user/tag/index.vue
  37. 420 14
      src/locales/en-US/index.json
  38. 6 0
      src/locales/navigation.js
  39. 416 13
      src/locales/zh-CN/index.json
  40. 235 231
      src/sheep/components/sa-table/sa-search/sa-search-simple.global.vue
  41. 25 22
      src/sheep/components/sa-uploader/uploader-cert.vue
  42. 99 116
      src/sheep/components/sa-uploader/uploader-input.vue
  43. 199 177
      src/sheep/layouts/setting.vue
  44. 383 330
      src/sheep/layouts/taskbar/index.vue
  45. 235 265
      src/sheep/views/login/index.vue

+ 2 - 2
.env.development

@@ -29,8 +29,8 @@ SHEEP_API_ROUTING_ENABLED = true
 
 # 各模块域名配置
 SHEEP_API_COMMON_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_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
 

+ 880 - 894
src/app/admin/views/auth/access/components/sa-access.vue

@@ -7,77 +7,47 @@
             <span>{{ val.pdata?.title }}</span>
             <slot name="add" :pdata="val.pdata">
               <template v-if="val.pdata">
-                <el-checkbox
-                  v-if="multiple"
-                  class="sa-m-r-8"
-                  v-model="val.pdata.checked"
-                  :indeterminate="val.pdata.indeterminate"
-                  label="全选"
-                  @update:model-value="handleSelect($event, val.pdata)"
-                />
+                <el-checkbox v-if="multiple" class="sa-m-r-8" v-model="val.pdata.checked"
+                  :indeterminate="val.pdata.indeterminate" :label="t('modules.auth.selectAll')"
+                  @update:model-value="handleSelect($event, val.pdata)" />
               </template>
               <template v-if="type == 'list'">
-                <el-button class="is-link" type="primary" @click="onAdd(val.pdata.id, level)"
-                  >+添加</el-button
-                >
+                <el-button class="is-link" type="primary" @click="onAdd(val.pdata.id, level)">{{
+                  t('modules.auth.addPermission') }}</el-button>
                 <!-- <el-button
                   class="is-link"
                   type="success"
                   @click="initMenuPermissions"
                   :loading="initLoading"
                 >
-                  初始化菜单
+                  {{ t('modules.auth.initMenu') }}
                 </el-button> -->
               </template>
             </slot>
           </div>
           <template v-if="val.data?.length > 0">
-            <sa-draggable
-              v-model="val.data"
-              :animation="300"
-              handle=".sortable-drag"
-              item-key="element"
-              @end="onEnd($event, level, val.pdata)"
-            >
+            <sa-draggable v-model="val.data" :animation="300" handle=".sortable-drag" item-key="element"
+              @end="onEnd($event, level, val.pdata)">
               <template #item="{ element, index }">
-                <div
-                  :class="['node', 'sa-flex sa-row-between', val.index == index ? 'is-active' : '']"
-                  @click="onClick(element, index, level)"
-                >
+                <div :class="['node', 'sa-flex sa-row-between', val.index == index ? 'is-active' : '']"
+                  @click="onClick(element, index, level)">
                   <!-- multiple -->
-                  <el-checkbox
-                    v-if="multiple"
-                    class="sa-m-r-8"
-                    v-model="element.checked"
-                    :indeterminate="element.indeterminate"
-                    @click.stop
-                    @change="handleSelect($event, element)"
-                  />
+                  <el-checkbox v-if="multiple" class="sa-m-r-8" v-model="element.checked"
+                    :indeterminate="element.indeterminate" @click.stop @change="handleSelect($event, element)" />
                   <slot class="label" :data="element" :level="level">
                     <div class="item sa-flex sa-row-between">
                       <div class="sa-flex">
-                        <sa-svg
-                          v-if="type == 'list'"
-                          class="sortable-drag sa-m-r-8"
-                          name="sa-round"
-                        ></sa-svg>
+                        <sa-svg v-if="type == 'list'" class="sortable-drag sa-m-r-8" name="sa-round"></sa-svg>
                         <sa-icon class="icon sa-m-r-4" :icon="element.icon" size="16" />
                         <div>{{ element.title }}</div>
                       </div>
                       <div v-if="type == 'list'" class="sa-flex">
-                        <el-icon
-                          class="edit sa-m-r-8"
-                          @click.stop="onEdit(element.id, index, level)"
-                        >
+                        <el-icon class="edit sa-m-r-8" @click.stop="onEdit(element.id, index, level)">
                           <Edit />
                         </el-icon>
-                        <el-popconfirm
-                          width="fit-content"
-                          confirm-button-text="确认"
-                          cancel-button-text="取消"
-                          title="确认删除这条记录?"
-                          @confirm="onDelete(element.id, index, level)"
-                        >
+                        <el-popconfirm width="fit-content" :confirm-button-text="t('common.confirm')"
+                          :cancel-button-text="t('common.cancel')" :title="t('modules.auth.confirmDeleteRecord')"
+                          @confirm="onDelete(element.id, index, level)">
                           <template #reference>
                             <el-icon class="delete sa-m-r-8" @click.stop>
                               <Delete />
@@ -97,7 +67,7 @@
             </sa-draggable>
           </template>
           <template v-if="!val.loading && val.data.length == 0">
-            <div class="empty">暂无数据</div>
+            <div class="empty">{{ t('modules.auth.noData') }}</div>
           </template>
         </el-scrollbar>
       </template>
@@ -106,1007 +76,1023 @@
 </template>
 
 <script setup>
-  import { nextTick, onMounted, reactive, watch, ref } from 'vue';
-  import admin from '@/app/admin/api';
-  import SaDraggable from 'vuedraggable';
-  import { useModal } from '@/sheep/hooks';
-  import { ElMessage, ElMessageBox } from 'element-plus';
-  import { menuRulesData } from '@/sheep/local-data/admin';
-
-  import { isEmpty } from 'lodash';
-  import AccessEdit from '../edit.vue';
-  const emit = defineEmits(['update:modelValue']);
-  const props = defineProps({
-    type: String,
-    isChangeParentId: Boolean,
-    role_id: {
-      type: [String, Number],
-      default: 0,
-    },
-    modelValue: {
-      type: Array,
-      default: [],
-    },
-    multiple: {
-      type: Boolean,
-      default: false,
-    },
-  });
-
-  let manualChecked = false;
-  let isSelectingPermission = false; // 新增标志,表示正在进行权限选择操作
-
-  const state = reactive({
-    loading: false,
-    app: [],
-    checkedIds: props.modelValue,
-    newIds: [],
-    show: {},
-  });
-
-  // 初始化loading状态
-  const initLoading = ref(false);
-  watch(
-    () => props.modelValue,
-    (newValue) => {
-      // 如果正在进行权限选择操作,只更新checkedIds,不触发其他计算
-      if (isSelectingPermission) {
-        state.checkedIds = newValue || [];
-        return;
-      }
-
+import { nextTick, onMounted, reactive, watch, ref } from 'vue';
+import admin from '@/app/admin/api';
+import SaDraggable from 'vuedraggable';
+import { useModal } from '@/sheep/hooks';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { menuRulesData } from '@/sheep/local-data/admin';
+import { useI18n } from 'vue-i18n';
+
+import { isEmpty } from 'lodash';
+import AccessEdit from '../edit.vue';
+
+const { t } = useI18n();
+const emit = defineEmits(['update:modelValue']);
+const props = defineProps({
+  type: String,
+  isChangeParentId: Boolean,
+  role_id: {
+    type: [String, Number],
+    default: 0,
+  },
+  modelValue: {
+    type: Array,
+    default: [],
+  },
+  multiple: {
+    type: Boolean,
+    default: false,
+  },
+});
+
+let manualChecked = false;
+let isSelectingPermission = false; // 新增标志,表示正在进行权限选择操作
+
+const state = reactive({
+  loading: false,
+  app: [],
+  checkedIds: props.modelValue,
+  newIds: [],
+  show: {},
+});
+
+// 初始化loading状态
+const initLoading = ref(false);
+watch(
+  () => props.modelValue,
+  (newValue) => {
+    // 如果正在进行权限选择操作,只更新checkedIds,不触发其他计算
+    if (isSelectingPermission) {
       state.checkedIds = newValue || [];
+      return;
+    }
 
-      // 当modelValue变化时,重新计算权限状态(无论数据是否为空都要计算)
-      if (state.app.length > 0) {
-        let appItem = initAppItem();
-        initCalculate(state.app, appItem);
-        calculateShow(appItem);
-      }
-    },
-    { immediate: true },
-  );
-
-  // 监听app数据变化,确保新增时也能显示
-  watch(
-    () => state.app,
-    (newApp) => {
-      // 如果正在进行权限选择操作,跳过所有处理
-      if (isSelectingPermission) {
-        return;
-      }
+    state.checkedIds = newValue || [];
 
-      // 无论数据是否为空都要初始化显示状态
+    // 当modelValue变化时,重新计算权限状态(无论数据是否为空都要计算)
+    if (state.app.length > 0) {
       let appItem = initAppItem();
-      if (newApp && newApp.length > 0) {
-        initCalculate(newApp, appItem);
-      }
-
+      initCalculate(state.app, appItem);
       calculateShow(appItem);
-    },
-  );
+    }
+  },
+  { immediate: true },
+);
+
+// 监听app数据变化,确保新增时也能显示
+watch(
+  () => state.app,
+  (newApp) => {
+    // 如果正在进行权限选择操作,跳过所有处理
+    if (isSelectingPermission) {
+      return;
+    }
 
-  // 数据转换函数:将新接口数据转换为组件需要的格式
-  function transformPermissionData(data) {
-    if (!Array.isArray(data)) {
-      console.warn('transformPermissionData: data is not an array', data);
-      return [];
+    // 无论数据是否为空都要初始化显示状态
+    let appItem = initAppItem();
+    if (newApp && newApp.length > 0) {
+      initCalculate(newApp, appItem);
     }
 
-    const result = data.map((item) => {
-      const transformed = {
-        id: item.id,
-        title: item.name || item.label, // 优先使用name,备用label
-        icon: item.logo || 'menu', // 使用logo作为icon,默认为menu
-        children:
-          item.children && Array.isArray(item.children)
-            ? transformPermissionData(item.children)
-            : [],
-        // 根据hasRelevance字段设置选中状态
-        checked: item.hasRelevance === '1',
-        indeterminate: false,
-        // 保留原始数据以备后用
-        _original: item,
-      };
-
-      console.log('🔄 transformPermissionData - 转换结果:', transformed);
-      return transformed;
-    });
+    calculateShow(appItem);
+  },
+);
 
-    console.log('🔄 transformPermissionData - 最终结果:', result);
-    return result;
+// 数据转换函数:将新接口数据转换为组件需要的格式
+function transformPermissionData(data) {
+  if (!Array.isArray(data)) {
+    console.warn('transformPermissionData: data is not an array', data);
+    return [];
   }
 
-  async function getData() {
-    state.loading = true;
+  const result = data.map((item) => {
+    const transformed = {
+      id: item.id,
+      title: item.name || item.label, // 优先使用name,备用label
+      icon: item.logo || 'menu', // 使用logo作为icon,默认为menu
+      children:
+        item.children && Array.isArray(item.children)
+          ? transformPermissionData(item.children)
+          : [],
+      // 根据hasRelevance字段设置选中状态
+      checked: item.hasRelevance === '1',
+      indeterminate: false,
+      // 保留原始数据以备后用
+      _original: item,
+    };
 
-    try {
-      let permissionData = [];
+    console.log('🔄 transformPermissionData - 转换结果:', transformed);
+    return transformed;
+  });
 
-      // 根据type类型获取不同的数据
-      if (props.type === 'select' && props.role_id) {
-        // 权限选择模式且有角色ID,获取角色详情中的权限数据
-        console.log('🔍 获取角色权限数据, role_id:', props.role_id);
-        const roleResponse = await admin.auth.role.detail(props.role_id);
+  console.log('🔄 transformPermissionData - 最终结果:', result);
+  return result;
+}
 
-        if (roleResponse.code == 200 && roleResponse.data && roleResponse.data.permissions) {
-          console.log('🔍 getData - 角色权限数据:', roleResponse.data.permissions);
-          permissionData = roleResponse.data.permissions;
-        }
-      } else {
-        // 其他模式,获取完整权限树
-        const response = await admin.auth.access.getTree();
+async function getData() {
+  state.loading = true;
 
-        if (response.success && response.data !== null && response.data !== undefined) {
-          console.log('🔍 getData - 权限树数据:', response.data);
-          permissionData = response.data;
-        }
+  try {
+    let permissionData = [];
+
+    // 根据type类型获取不同的数据
+    if (props.type === 'select' && props.role_id) {
+      // 权限选择模式且有角色ID,获取角色详情中的权限数据
+      console.log('🔍 获取角色权限数据, role_id:', props.role_id);
+      const roleResponse = await admin.auth.role.detail(props.role_id);
+
+      if (roleResponse.code == 200 && roleResponse.data && roleResponse.data.permissions) {
+        console.log('🔍 getData - 角色权限数据:', roleResponse.data.permissions);
+        permissionData = roleResponse.data.permissions;
       }
+    } else {
+      // 其他模式,获取完整权限树
+      const response = await admin.auth.access.getTree();
 
-      // 转换数据格式(即使是空数组也要处理)
-      const transformedData = Array.isArray(permissionData)
-        ? transformPermissionData(permissionData)
-        : [];
+      if (response.success && response.data !== null && response.data !== undefined) {
+        console.log('🔍 getData - 权限树数据:', response.data);
+        permissionData = response.data;
+      }
+    }
 
-      console.log('🔍 getData - 转换后数据:', transformedData);
+    // 转换数据格式(即使是空数组也要处理)
+    const transformedData = Array.isArray(permissionData)
+      ? transformPermissionData(permissionData)
+      : [];
 
-      state.app = transformedData;
+    console.log('🔍 getData - 转换后数据:', transformedData);
 
-      // 初始化显示状态(无论数据是否为空都要初始化)
-      let appItem = initAppItem();
-      if (transformedData.length > 0) {
-        initCalculate(transformedData, appItem);
-      }
+    state.app = transformedData;
 
-      // 如果不是权限选择操作导致的变化,才重置展开状态
-      if (!isSelectingPermission) {
-        calculateShow(appItem);
-      }
-    } catch (error) {
-      console.error('获取权限数据异常:', error);
-      state.app = [];
+    // 初始化显示状态(无论数据是否为空都要初始化)
+    let appItem = initAppItem();
+    if (transformedData.length > 0) {
+      initCalculate(transformedData, appItem);
+    }
 
-      // 异常情况下也要初始化显示状态
-      let appItem = initAppItem();
-      if (!isSelectingPermission) {
-        calculateShow(appItem);
-      }
+    // 如果不是权限选择操作导致的变化,才重置展开状态
+    if (!isSelectingPermission) {
+      calculateShow(appItem);
     }
+  } catch (error) {
+    console.error('获取权限数据异常:', error);
+    state.app = [];
 
-    state.loading = false;
+    // 异常情况下也要初始化显示状态
+    let appItem = initAppItem();
+    if (!isSelectingPermission) {
+      calculateShow(appItem);
+    }
   }
 
-  // 🎯 新方案:保持选中状态的数据刷新函数
-  async function refreshDataKeepSelected() {
-    // 1. 保存当前所有层级的选中状态
-    const selectedStates = [];
-
-    for (let level = 0; level < Object.keys(state.show).length; level++) {
-      const showData = state.show[level];
-      if (showData && showData.index !== null && showData.index !== undefined && showData.data) {
-        const selectedItem = showData.data[showData.index];
-        if (selectedItem && selectedItem.id) {
-          selectedStates.push({
-            level: level,
-            itemId: selectedItem.id,
-            itemTitle: selectedItem.title,
-            selectedIndex: showData.index,
-          });
-          console.log(
-            `💾 保存第${level}层选中状态: ${selectedItem.title} (ID: ${selectedItem.id}, 索引: ${showData.index})`,
-          );
-        }
+  state.loading = false;
+}
+
+// 🎯 新方案:保持选中状态的数据刷新函数
+async function refreshDataKeepSelected() {
+  // 1. 保存当前所有层级的选中状态
+  const selectedStates = [];
+
+  for (let level = 0; level < Object.keys(state.show).length; level++) {
+    const showData = state.show[level];
+    if (showData && showData.index !== null && showData.index !== undefined && showData.data) {
+      const selectedItem = showData.data[showData.index];
+      if (selectedItem && selectedItem.id) {
+        selectedStates.push({
+          level: level,
+          itemId: selectedItem.id,
+          itemTitle: selectedItem.title,
+          selectedIndex: showData.index,
+        });
+        console.log(
+          `💾 保存第${level}层选中状态: ${selectedItem.title} (ID: ${selectedItem.id}, 索引: ${showData.index})`,
+        );
       }
     }
+  }
 
-    console.log('💾 保存的选中状态:', selectedStates);
+  console.log('💾 保存的选中状态:', selectedStates);
 
-    // 2. 重新获取数据(这会重置state.show)
-    await getData();
+  // 2. 重新获取数据(这会重置state.show)
+  await getData();
 
-    // 3. 恢复选中状态
-    if (selectedStates.length > 0) {
-      console.log('🔄 开始恢复选中状态...');
+  // 3. 恢复选中状态
+  if (selectedStates.length > 0) {
+    console.log('🔄 开始恢复选中状态...');
 
-      // 逐层恢复选中状态
-      for (const selectedState of selectedStates) {
-        const { level, itemId, itemTitle } = selectedState;
-        console.log(`🔄 恢复第${level}层选中状态: ${itemTitle} (ID: ${itemId})`);
+    // 逐层恢复选中状态
+    for (const selectedState of selectedStates) {
+      const { level, itemId, itemTitle } = selectedState;
+      console.log(`🔄 恢复第${level}层选中状态: ${itemTitle} (ID: ${itemId})`);
 
-        // 在当前层级查找目标项目
-        const currentLevelData = state.show[level] ? state.show[level].data : [];
-        const foundIndex = currentLevelData.findIndex((item) => item.id === itemId);
+      // 在当前层级查找目标项目
+      const currentLevelData = state.show[level] ? state.show[level].data : [];
+      const foundIndex = currentLevelData.findIndex((item) => item.id === itemId);
 
-        if (foundIndex !== -1) {
-          const foundItem = currentLevelData[foundIndex];
-          console.log(`✅ 找到目标项目: ${foundItem.title} (索引: ${foundIndex})`);
+      if (foundIndex !== -1) {
+        const foundItem = currentLevelData[foundIndex];
+        console.log(`✅ 找到目标项目: ${foundItem.title} (索引: ${foundIndex})`);
 
-          // 使用calculateShow来正确展开并选中这一层
-          await calculateShow(foundItem, foundIndex, level);
+        // 使用calculateShow来正确展开并选中这一层
+        await calculateShow(foundItem, foundIndex, level);
 
-          console.log(`✅ 第${level}层选中状态恢复完成`);
-        } else {
-          console.log(`⚠️ 第${level}层找不到目标项目 ID: ${itemId}`);
-          break; // 如果某一层找不到,停止恢复
-        }
+        console.log(`✅ 第${level}层选中状态恢复完成`);
+      } else {
+        console.log(`⚠️ 第${level}层找不到目标项目 ID: ${itemId}`);
+        break; // 如果某一层找不到,停止恢复
       }
     }
+  }
 
-    console.log('✅ 选中状态恢复完成');
+  console.log('✅ 选中状态恢复完成');
+}
+
+// 🎯 新方案:局部更新数据函数
+async function updateLocalData(operation, newData, parentId, targetId) {
+  console.log(`🔄 局部更新数据: ${operation}`, { newData, parentId, targetId });
+
+  if (operation === 'add') {
+    // 新增:将新数据添加到指定父级的children中
+    await handleLocalAdd(newData, parentId);
+  } else if (operation === 'edit') {
+    // 编辑:找到目标项目并更新其数据
+    await handleLocalEdit(newData, targetId);
+  } else if (operation === 'delete') {
+    // 删除:从数据中移除目标项目
+    await handleLocalDelete(targetId);
   }
 
-  // 🎯 新方案:局部更新数据函数
-  async function updateLocalData(operation, newData, parentId, targetId) {
-    console.log(`🔄 局部更新数据: ${operation}`, { newData, parentId, targetId });
-
-    if (operation === 'add') {
-      // 新增:将新数据添加到指定父级的children中
-      await handleLocalAdd(newData, parentId);
-    } else if (operation === 'edit') {
-      // 编辑:找到目标项目并更新其数据
-      await handleLocalEdit(newData, targetId);
-    } else if (operation === 'delete') {
-      // 删除:从数据中移除目标项目
-      await handleLocalDelete(targetId);
-    }
+  console.log('✅ 局部数据更新完成');
+}
 
-    console.log('✅ 局部数据更新完成');
+// 处理新增操作
+async function handleLocalAdd(newData, parentId) {
+  console.log(`📝 处理新增: 父级ID=${parentId}`, newData);
+
+  if (!newData) {
+    console.error('❌ 新增数据为空');
+    return;
   }
 
-  // 处理新增操作
-  async function handleLocalAdd(newData, parentId) {
-    console.log(`📝 处理新增: 父级ID=${parentId}`, newData);
+  // 转换新数据格式
+  const transformedNewData = {
+    id: newData.id,
+    title: newData.name || '新权限',
+    icon: newData.logo || 'menu',
+    children: [],
+    _original: newData,
+  };
 
-    if (!newData) {
-      console.error('❌ 新增数据为空');
-      return;
+  if (!parentId || parentId === '' || parentId === '0') {
+    // 添加到根级
+    state.app.push(transformedNewData);
+    // 更新第0层的数据
+    if (state.show[0]) {
+      state.show[0].data = state.app;
     }
-
-    // 转换新数据格式
-    const transformedNewData = {
-      id: newData.id,
-      title: newData.name || '新权限',
-      icon: newData.logo || 'menu',
-      children: [],
-      _original: newData,
-    };
-
-    if (!parentId || parentId === '' || parentId === '0') {
-      // 添加到根级
-      state.app.push(transformedNewData);
-      // 更新第0层的数据
-      if (state.show[0]) {
-        state.show[0].data = state.app;
+    console.log('✅ 已添加到根级');
+  } else {
+    // 添加到指定父级
+    const parentItem = findItemById(state.app, parentId);
+    if (parentItem) {
+      if (!parentItem.children) {
+        parentItem.children = [];
       }
-      console.log('✅ 已添加到根级');
-    } else {
-      // 添加到指定父级
-      const parentItem = findItemById(state.app, parentId);
-      if (parentItem) {
-        if (!parentItem.children) {
-          parentItem.children = [];
-        }
-        parentItem.children.push(transformedNewData);
+      parentItem.children.push(transformedNewData);
 
-        // 更新对应层级的显示数据
-        updateShowDataForParent(parentId, parentItem.children);
-        console.log(`✅ 已添加到父级 ${parentId}`);
-      } else {
-        console.error(`❌ 找不到父级项目 ID: ${parentId}`);
-      }
+      // 更新对应层级的显示数据
+      updateShowDataForParent(parentId, parentItem.children);
+      console.log(`✅ 已添加到父级 ${parentId}`);
+    } else {
+      console.error(`❌ 找不到父级项目 ID: ${parentId}`);
     }
   }
+}
 
-  // 处理编辑操作
-  async function handleLocalEdit(newData, targetId) {
-    console.log(`✏️ 处理编辑: 目标ID=${targetId}`, newData);
-
-    if (!newData) {
-      console.error('❌ 编辑数据为空');
-      return;
-    }
-
-    // 🔧 验证数据格式
-    if (!newData.name && !newData.id) {
-      console.error('❌ 编辑数据格式不正确,缺少必要字段:', newData);
-      return;
-    }
+// 处理编辑操作
+async function handleLocalEdit(newData, targetId) {
+  console.log(`✏️ 处理编辑: 目标ID=${targetId}`, newData);
 
-    const targetItem = findItemById(state.app, targetId);
-    if (targetItem) {
-      // 更新项目数据
-      targetItem.title = newData.name || newData.label || targetItem.title;
-      targetItem.icon = newData.logo || newData.icon || targetItem.icon;
-      targetItem._original = { ...targetItem._original, ...newData };
-
-      // 更新所有相关的显示数据
-      updateAllShowData();
-      console.log(`✅ 已更新项目 ${targetId}:`, {
-        title: targetItem.title,
-        icon: targetItem.icon,
-      });
-    } else {
-      console.error(`❌ 找不到目标项目 ID: ${targetId}`);
-    }
+  if (!newData) {
+    console.error('❌ 编辑数据为空');
+    return;
   }
 
-  // 处理删除操作
-  async function handleLocalDelete(targetId) {
-    console.log(`🗑️ 处理删除: 目标ID=${targetId}`);
+  // 🔧 验证数据格式
+  if (!newData.name && !newData.id) {
+    console.error('❌ 编辑数据格式不正确,缺少必要字段:', newData);
+    return;
+  }
 
-    const result = removeItemById(state.app, targetId);
-    if (result.success) {
-      // 更新所有相关的显示数据
-      updateAllShowData();
-      console.log(`✅ 已删除项目 ${targetId}`);
-    } else {
-      console.error(`❌ 找不到要删除的项目 ID: ${targetId}`);
-    }
+  const targetItem = findItemById(state.app, targetId);
+  if (targetItem) {
+    // 更新项目数据
+    targetItem.title = newData.name || newData.label || targetItem.title;
+    targetItem.icon = newData.logo || newData.icon || targetItem.icon;
+    targetItem._original = { ...targetItem._original, ...newData };
+
+    // 更新所有相关的显示数据
+    updateAllShowData();
+    console.log(`✅ 已更新项目 ${targetId}:`, {
+      title: targetItem.title,
+      icon: targetItem.icon,
+    });
+  } else {
+    console.error(`❌ 找不到目标项目 ID: ${targetId}`);
   }
+}
+
+// 处理删除操作
+async function handleLocalDelete(targetId) {
+  console.log(`🗑️ 处理删除: 目标ID=${targetId}`);
+
+  const result = removeItemById(state.app, targetId);
+  if (result.success) {
+    // 更新所有相关的显示数据
+    updateAllShowData();
+    console.log(`✅ 已删除项目 ${targetId}`);
+  } else {
+    console.error(`❌ 找不到要删除的项目 ID: ${targetId}`);
+  }
+}
 
-  // 递归查找项目
-  function findItemById(items, targetId) {
-    for (const item of items) {
-      if (item.id === targetId) {
-        return item;
-      }
-      if (item.children && item.children.length > 0) {
-        const found = findItemById(item.children, targetId);
-        if (found) return found;
-      }
+// 递归查找项目
+function findItemById(items, targetId) {
+  for (const item of items) {
+    if (item.id === targetId) {
+      return item;
+    }
+    if (item.children && item.children.length > 0) {
+      const found = findItemById(item.children, targetId);
+      if (found) return found;
     }
-    return null;
   }
-
-  // 递归删除项目
-  function removeItemById(items, targetId) {
-    for (let i = 0; i < items.length; i++) {
-      if (items[i].id === targetId) {
-        items.splice(i, 1);
-        return { success: true };
-      }
-      if (items[i].children && items[i].children.length > 0) {
-        const result = removeItemById(items[i].children, targetId);
-        if (result.success) return result;
-      }
+  return null;
+}
+
+// 递归删除项目
+function removeItemById(items, targetId) {
+  for (let i = 0; i < items.length; i++) {
+    if (items[i].id === targetId) {
+      items.splice(i, 1);
+      return { success: true };
+    }
+    if (items[i].children && items[i].children.length > 0) {
+      const result = removeItemById(items[i].children, targetId);
+      if (result.success) return result;
     }
-    return { success: false };
   }
-
-  // 更新指定父级的显示数据
-  function updateShowDataForParent(parentId, newChildren) {
-    // 遍历所有层级,找到对应的父级并更新其子级数据
-    for (const [level, showData] of Object.entries(state.show)) {
-      if (showData.data) {
-        const parentItem = showData.data.find((item) => item.id === parentId);
-        if (parentItem) {
-          // 找到了父级,更新下一层级的数据
-          const nextLevel = parseInt(level) + 1;
-          if (state.show[nextLevel]) {
-            state.show[nextLevel].data = newChildren;
-          }
-          break;
+  return { success: false };
+}
+
+// 更新指定父级的显示数据
+function updateShowDataForParent(parentId, newChildren) {
+  // 遍历所有层级,找到对应的父级并更新其子级数据
+  for (const [level, showData] of Object.entries(state.show)) {
+    if (showData.data) {
+      const parentItem = showData.data.find((item) => item.id === parentId);
+      if (parentItem) {
+        // 找到了父级,更新下一层级的数据
+        const nextLevel = parseInt(level) + 1;
+        if (state.show[nextLevel]) {
+          state.show[nextLevel].data = newChildren;
         }
+        break;
       }
     }
   }
+}
 
-  // 更新所有显示数据
-  function updateAllShowData() {
-    // 更新第0层
-    if (state.show[0]) {
-      state.show[0].data = state.app;
-    }
+// 更新所有显示数据
+function updateAllShowData() {
+  // 更新第0层
+  if (state.show[0]) {
+    state.show[0].data = state.app;
+  }
 
-    // 更新其他层级
-    for (const [level, showData] of Object.entries(state.show)) {
-      if (parseInt(level) > 0 && showData.pdata && showData.pdata.children) {
-        showData.data = showData.pdata.children;
-      }
+  // 更新其他层级
+  for (const [level, showData] of Object.entries(state.show)) {
+    if (parseInt(level) > 0 && showData.pdata && showData.pdata.children) {
+      showData.data = showData.pdata.children;
     }
   }
-
-  function initAppItem() {
-    let appItem = {
-      id: 0,
-      title: '应用',
-      checked: true,
-      indeterminate: false,
-      children: state.app,
-    };
-    let allData = [];
-    flattenData(state.app, allData);
-    if (state.checkedIds.length == 0) {
-      appItem.checked = false;
+}
+
+function initAppItem() {
+  let appItem = {
+    id: 0,
+    title: t('modules.auth.application'),
+    checked: true,
+    indeterminate: false,
+    children: state.app,
+  };
+  let allData = [];
+  flattenData(state.app, allData);
+  if (state.checkedIds.length == 0) {
+    appItem.checked = false;
+    appItem.indeterminate = false;
+  } else {
+    if (allData.length == state.checkedIds.length) {
+      appItem.checked = true;
       appItem.indeterminate = false;
     } else {
-      if (allData.length == state.checkedIds.length) {
-        appItem.checked = true;
-        appItem.indeterminate = false;
-      } else {
-        appItem.checked = false;
-        appItem.indeterminate = true;
-      }
+      appItem.checked = false;
+      appItem.indeterminate = true;
     }
-    return appItem;
   }
+  return appItem;
+}
+
+// 初始化选中数据
+function initCalculate(data, parent = {}) {
+  data.forEach((item) => {
+    item.parent = parent;
+
+    // 兼容数字和字符串ID的比较
+    const itemIdStr = String(item.id);
+    const itemIdNum = Number(item.id);
+    const isChecked = state.checkedIds.some((checkedId) => {
+      const checkedIdStr = String(checkedId);
+      const checkedIdNum = Number(checkedId);
+      return checkedIdStr === itemIdStr || checkedIdNum === itemIdNum;
+    });
 
-  // 初始化选中数据
-  function initCalculate(data, parent = {}) {
-    data.forEach((item) => {
-      item.parent = parent;
-
-      // 兼容数字和字符串ID的比较
-      const itemIdStr = String(item.id);
-      const itemIdNum = Number(item.id);
-      const isChecked = state.checkedIds.some((checkedId) => {
-        const checkedIdStr = String(checkedId);
-        const checkedIdNum = Number(checkedId);
-        return checkedIdStr === itemIdStr || checkedIdNum === itemIdNum;
-      });
+    // 先递归处理子项
+    if (!isEmpty(item.children)) {
+      initCalculate(item.children, item);
 
-      // 先递归处理子项
-      if (!isEmpty(item.children)) {
-        initCalculate(item.children, item);
-
-        // 检查子项的选中状态
-        const checkedChildren = item.children.filter((child) => child.checked);
-        const indeterminateChildren = item.children.filter((child) => child.indeterminate);
-
-        // 父级状态完全根据子级状态来判断,不考虑父级本身是否在选中列表中
-        if (checkedChildren.length === item.children.length) {
-          // 所有子项都选中 - 父级全选
-          item.checked = true;
-          item.indeterminate = false;
-        } else if (checkedChildren.length > 0 || indeterminateChildren.length > 0) {
-          // 部分子项选中或有半选状态 - 父级半选
-          item.checked = false;
-          item.indeterminate = true;
-        } else {
-          // 没有子项选中 - 父级未选
-          item.checked = false;
-          item.indeterminate = false;
-        }
+      // 检查子项的选中状态
+      const checkedChildren = item.children.filter((child) => child.checked);
+      const indeterminateChildren = item.children.filter((child) => child.indeterminate);
+
+      // 父级状态完全根据子级状态来判断,不考虑父级本身是否在选中列表中
+      if (checkedChildren.length === item.children.length) {
+        // 所有子项都选中 - 父级全选
+        item.checked = true;
+        item.indeterminate = false;
+      } else if (checkedChildren.length > 0 || indeterminateChildren.length > 0) {
+        // 部分子项选中或有半选状态 - 父级半选
+        item.checked = false;
+        item.indeterminate = true;
       } else {
-        // 叶子节点,直接根据是否在选中列表中设置状态
-        item.checked = isChecked;
+        // 没有子项选中 - 父级未选
+        item.checked = false;
         item.indeterminate = false;
       }
-    });
-  }
-
-  // 扁平化数据
-  function flattenData(data, arr) {
-    data.forEach((item) => {
-      arr.push(item);
-      if (!isEmpty(item.children)) {
-        flattenData(item.children, arr);
-      }
-    });
-  }
-
-  async function calculateShow(item, index = null, level = 0) {
-    // 选中展开
-    if (level != 0) {
-      state.show[level].index = index;
+    } else {
+      // 叶子节点,直接根据是否在选中列表中设置状态
+      item.checked = isChecked;
+      item.indeterminate = false;
     }
+  });
+}
 
-    // 清除多余数据
-    for (let key in state.show) {
-      if (key > level) {
-        delete state.show[key];
-      }
+// 扁平化数据
+function flattenData(data, arr) {
+  data.forEach((item) => {
+    arr.push(item);
+    if (!isEmpty(item.children)) {
+      flattenData(item.children, arr);
     }
+  });
+}
 
-    // loading
-    state.show[Number(level) + 1] = {
-      loading: true,
-    };
+async function calculateShow(item, index = null, level = 0) {
+  // 选中展开
+  if (level != 0) {
+    state.show[level].index = index;
+  }
 
-    if (isEmpty(item.children)) {
-      item.children = [];
+  // 清除多余数据
+  for (let key in state.show) {
+    if (key > level) {
+      delete state.show[key];
     }
+  }
 
-    state.show[Number(level) + 1] = {
-      index: null,
-      data: item.children,
-      pdata: item,
-      loading: false,
-    };
+  // loading
+  state.show[Number(level) + 1] = {
+    loading: true,
+  };
+
+  if (isEmpty(item.children)) {
+    item.children = [];
   }
 
-  async function onClick(item, index, level) {
-    calculateShow(item, index, level);
-    nextTick(() => {
-      let left =
-        document.getElementById('scrollWrap').scrollWidth -
-        document.getElementById('scrollWrap').offsetWidth;
-      document.getElementById('scrollWrap').scrollTo({
-        top: 0,
-        left: left,
-        behavior: 'smooth',
-      });
+  state.show[Number(level) + 1] = {
+    index: null,
+    data: item.children,
+    pdata: item,
+    loading: false,
+  };
+}
+
+async function onClick(item, index, level) {
+  calculateShow(item, index, level);
+  nextTick(() => {
+    let left =
+      document.getElementById('scrollWrap').scrollWidth -
+      document.getElementById('scrollWrap').offsetWidth;
+    document.getElementById('scrollWrap').scrollTo({
+      top: 0,
+      left: left,
+      behavior: 'smooth',
+    });
+  });
+}
+
+function handleSelect(checked, item) {
+  // 设置标志,阻止watch触发
+  isSelectingPermission = true;
+  manualChecked = true;
+
+  // 计算所有子元素
+  doChecked(item, checked);
+
+  // 计算父元素
+  doCheckedParent(item);
+
+  // 收集选中的权限ID
+  const newIds = [];
+  getCheckedIds(state.app, newIds);
+
+  // 直接发送事件,不更新state.newIds避免触发watch
+  emit('update:modelValue', newIds);
+
+  // 延迟重置标志
+  setTimeout(() => {
+    isSelectingPermission = false;
+  }, 50);
+}
+
+function doChecked(item, checked) {
+  item.checked = checked;
+  item.indeterminate = false;
+  if (!isEmpty(item.children)) {
+    item.children.forEach((i) => {
+      i.checked = checked;
+      i.indeterminate = false;
+      doChecked(i, checked);
     });
   }
+}
+
+function doCheckedParent(i) {
+  // 如果有父级
+  if (!isEmpty(i.parent)) {
+    if (!isEmpty(i.parent.children)) {
+      // 部分选中
+      i.parent.checked = false;
+      i.parent.indeterminate = true;
+
+      // 全选中
+      if (i.parent.children.every((k) => k.checked)) {
+        i.parent.checked = true;
+        i.parent.indeterminate = false;
+      }
 
-  function handleSelect(checked, item) {
-    // 设置标志,阻止watch触发
-    isSelectingPermission = true;
-    manualChecked = true;
-
-    // 计算所有子元素
-    doChecked(item, checked);
-
-    // 计算父元素
-    doCheckedParent(item);
-
-    // 收集选中的权限ID
-    const newIds = [];
-    getCheckedIds(state.app, newIds);
+      // 未选中
+      if (!i.parent.children.some((k) => k.checked || k.indeterminate)) {
+        i.parent.checked = false;
+        i.parent.indeterminate = false;
+      }
+    }
+    doCheckedParent(i.parent);
+  }
+}
 
-    // 直接发送事件,不更新state.newIds避免触发watch
-    emit('update:modelValue', newIds);
+async function onEnd(e, level, pdata) {
+  if (e.newIndex != e.oldIndex) {
+    try {
+      // 拖动元素修改seq(排序)
+      const draggedItem = pdata.children[e.oldIndex];
+      const targetItem = pdata.children[e.newIndex];
 
-    // 延迟重置标志
-    setTimeout(() => {
-      isSelectingPermission = false;
-    }, 50);
-  }
+      // 更新排序值
+      const newSeq = targetItem._original?.seq ? targetItem._original.seq - 1 : e.newIndex - 1;
 
-  function doChecked(item, checked) {
-    item.checked = checked;
-    item.indeterminate = false;
-    if (!isEmpty(item.children)) {
-      item.children.forEach((i) => {
-        i.checked = checked;
-        i.indeterminate = false;
-        doChecked(i, checked);
+      // 调用编辑接口更新排序
+      await admin.auth.access.edit({
+        id: draggedItem.id,
+        seq: newSeq,
       });
+
+      // 🎯 新方案:拖拽排序后保持选中状态刷新
+      console.log('✅ 拖拽排序成功,保持选中状态刷新数据');
+      await refreshDataKeepSelected();
+    } catch (error) {
+      console.error('更新排序失败:', error);
+      // 如果更新失败,重新获取数据恢复状态
+      await getData();
     }
   }
-
-  function doCheckedParent(i) {
-    // 如果有父级
-    if (!isEmpty(i.parent)) {
-      if (!isEmpty(i.parent.children)) {
-        // 部分选中
-        i.parent.checked = false;
-        i.parent.indeterminate = true;
-
-        // 全选中
-        if (i.parent.children.every((k) => k.checked)) {
-          i.parent.checked = true;
-          i.parent.indeterminate = false;
+}
+function onAdd(id = null, level) {
+  useModal(
+    AccessEdit,
+    {
+      title: t('common.add'),
+      type: 'add',
+      parent_id: id,
+    },
+    {
+      confirm: async (result) => {
+        console.log('📝 新增回调数据:', result);
+
+        if (result && result.event === 'confirm') {
+          // 🎯 新方案:新增操作成功,保持选中状态刷新
+          console.log('✅ 新增操作成功,保持选中状态刷新数据');
+          await refreshDataKeepSelected();
+        } else {
+          console.warn('⚠️ 新增操作未确认,不执行任何操作');
         }
-
-        // 未选中
-        if (!i.parent.children.some((k) => k.checked || k.indeterminate)) {
-          i.parent.checked = false;
-          i.parent.indeterminate = false;
+      },
+    },
+  );
+}
+function onEdit(id, index, level) {
+  useModal(
+    AccessEdit,
+    {
+      title: t('common.edit'),
+      type: 'edit',
+      id: id,
+    },
+    {
+      confirm: async (result) => {
+        console.log('📝 编辑回调数据:', result);
+
+        if (result && result.event === 'confirm') {
+          // 🎯 新方案:编辑操作成功,保持选中状态刷新
+          console.log('✅ 编辑操作成功,保持选中状态刷新数据');
+          await refreshDataKeepSelected();
+        } else {
+          console.warn('⚠️ 编辑操作未确认,不执行任何操作');
         }
-      }
-      doCheckedParent(i.parent);
+      },
+    },
+  );
+}
+
+async function onDelete(id, index, level) {
+  try {
+    const { code } = await admin.auth.access.delete({ id });
+    if (code == '200') {
+      // 🎯 新方案:删除操作成功,保持选中状态刷新
+      console.log('✅ 删除操作成功,保持选中状态刷新数据');
+      await refreshDataKeepSelected();
     }
+  } catch (error) {
+    console.error('删除权限失败:', error);
   }
+}
 
-  async function onEnd(e, level, pdata) {
-    if (e.newIndex != e.oldIndex) {
-      try {
-        // 拖动元素修改seq(排序)
-        const draggedItem = pdata.children[e.oldIndex];
-        const targetItem = pdata.children[e.newIndex];
+// 移除可能导致递归更新的watch监听器
+// 权限选择的更新通过handleSelect函数直接处理
 
-        // 更新排序值
-        const newSeq = targetItem._original?.seq ? targetItem._original.seq - 1 : e.newIndex - 1;
+watch(
+  () => props.role_id,
+  () => {
+    if (props.isChangeParentId) {
+      state.checkedIds = [];
+    }
+    getData();
+  },
+);
 
-        // 调用编辑接口更新排序
-        await admin.auth.access.edit({
-          id: draggedItem.id,
-          seq: newSeq,
-        });
+function getCheckedIds(data, targetArray = null) {
+  const idsArray = targetArray || state.newIds;
+
+  data.forEach((i) => {
+    // 收集真正选中的权限(全选状态)
+    if (i.checked && !i.indeterminate) {
+      idsArray.push(i.id + '');
+    }
+    // 收集半选状态的父级权限(当子级被选中时,父级也要包含)
+    else if (i.indeterminate) {
+      idsArray.push(i.id + '');
+    }
 
-        // 🎯 新方案:拖拽排序后保持选中状态刷新
-        console.log('✅ 拖拽排序成功,保持选中状态刷新数据');
-        await refreshDataKeepSelected();
+    if (!isEmpty(i.children)) {
+      getCheckedIds(i.children, idsArray);
+    }
+  });
+}
+
+// 递归收集所有权限ID
+const collectAllPermissionIds = (items) => {
+  const ids = [];
+  items.forEach((item) => {
+    ids.push(item.id);
+    if (item.children && item.children.length > 0) {
+      ids.push(...collectAllPermissionIds(item.children));
+    }
+  });
+  return ids;
+};
+
+// 清空所有现有权限
+const clearAllPermissions = async () => {
+  console.log('🗑️ 开始清空现有权限...');
+
+  // 获取现有权限树
+  const response = await admin.auth.access.getTree();
+  if (response.success && response.data && response.data.length > 0) {
+    // 收集所有权限ID
+    const allIds = collectAllPermissionIds(response.data);
+    console.log(`📋 找到 ${allIds.length} 个权限需要删除`);
+
+    // 逐个删除(从子级开始删除,避免外键约束问题)
+    for (let i = allIds.length - 1; i >= 0; i--) {
+      const id = allIds[i];
+      try {
+        const deleteResponse = await admin.auth.access.delete({ id });
+        if (deleteResponse.code === '200') {
+          console.log(`✅ 删除权限成功: ID ${id}`);
+        } else {
+          console.warn(`⚠️ 删除权限失败: ID ${id}`, deleteResponse);
+        }
       } catch (error) {
-        console.error('更新排序失败:', error);
-        // 如果更新失败,重新获取数据恢复状态
-        await getData();
+        console.warn(`⚠️ 删除权限异常: ID ${id}`, error);
       }
+
+      // 延迟避免请求过快
+      await new Promise((resolve) => setTimeout(resolve, 100));
     }
+
+    console.log('🎯 现有权限清空完成');
+  } else {
+    console.log('📝 没有找到现有权限,跳过清空步骤');
   }
-  function onAdd(id = null, level) {
-    useModal(
-      AccessEdit,
+};
+
+// 菜单权限初始化方法
+const initMenuPermissions = async () => {
+  try {
+    // 确认对话框
+    await ElMessageBox.confirm(
+      t('modules.auth.confirmInit'),
+      t('modules.auth.initConfirmTitle'),
       {
-        title: '新建',
-        type: 'add',
-        parent_id: id,
-      },
-      {
-        confirm: async (result) => {
-          console.log('📝 新增回调数据:', result);
-
-          if (result && result.event === 'confirm') {
-            // 🎯 新方案:新增操作成功,保持选中状态刷新
-            console.log('✅ 新增操作成功,保持选中状态刷新数据');
-            await refreshDataKeepSelected();
-          } else {
-            console.warn('⚠️ 新增操作未确认,不执行任何操作');
-          }
-        },
+        confirmButtonText: t('common.confirm'),
+        cancelButtonText: t('common.cancel'),
+        type: 'warning',
+        dangerouslyUseHTMLString: true,
       },
     );
-  }
-  function onEdit(id, index, level) {
-    useModal(
-      AccessEdit,
-      {
-        title: '编辑',
-        type: 'edit',
-        id: id,
-      },
-      {
-        confirm: async (result) => {
-          console.log('📝 编辑回调数据:', result);
-
-          if (result && result.event === 'confirm') {
-            // 🎯 新方案:编辑操作成功,保持选中状态刷新
-            console.log('✅ 编辑操作成功,保持选中状态刷新数据');
-            await refreshDataKeepSelected();
-          } else {
-            console.warn('⚠️ 编辑操作未确认,不执行任何操作');
-          }
-        },
-      },
-    );
-  }
 
-  async function onDelete(id, index, level) {
-    try {
-      const { code } = await admin.auth.access.delete({ id });
-      if (code == '200') {
-        // 🎯 新方案:删除操作成功,保持选中状态刷新
-        console.log('✅ 删除操作成功,保持选中状态刷新数据');
-        await refreshDataKeepSelected();
-      }
-    } catch (error) {
-      console.error('删除权限失败:', error);
-    }
-  }
+    initLoading.value = true;
+    console.log('🚀 开始菜单权限初始化...');
 
-  // 移除可能导致递归更新的watch监听器
-  // 权限选择的更新通过handleSelect函数直接处理
+    // 1. 首先清空所有现有权限
+    await clearAllPermissions();
 
-  watch(
-    () => props.role_id,
-    () => {
-      if (props.isChangeParentId) {
-        state.checkedIds = [];
-      }
-      getData();
-    },
-  );
+    // 数据转换函数
+    const transformMenuData = (item, parentId = '') => ({
+      id: 0,
+      parentId: parentId,
+      name: item.title,
+      eName: item.name,
+      logo: item.icon || '',
+      composingKey: item.name,
+      type: item.type === 'menu' ? 0 : item.type === 'page' ? 1 : 2,
+      status: item.status === 'show' ? 0 : 1,
+      seq: item.weigh || 0,
+      url: '',
+      isAction: item.type === 'api' ? '1' : '0',
+      children: [],
+      createTime: '',
+      createUserId: '',
+      updateTime: '',
+      updateUserId: '',
+    });
 
-  function getCheckedIds(data, targetArray = null) {
-    const idsArray = targetArray || state.newIds;
+    // 根据权限名称和父ID查找新创建的权限ID
+    const findPermissionId = async (name, parentId = '') => {
+      console.log(`🔍 查找权限: ${name}, 父ID: "${parentId}"`);
 
-    data.forEach((i) => {
-      // 收集真正选中的权限(全选状态)
-      if (i.checked && !i.indeterminate) {
-        idsArray.push(i.id + '');
-      }
-      // 收集半选状态的父级权限(当子级被选中时,父级也要包含)
-      else if (i.indeterminate) {
-        idsArray.push(i.id + '');
-      }
+      const response = await admin.auth.access.getTree();
+      if (response.success && response.data) {
+        console.log(`🔍 获取到的权限树数据:`, response.data);
 
-      if (!isEmpty(i.children)) {
-        getCheckedIds(i.children, idsArray);
-      }
-    });
-  }
+        // 🎯 关键修复:对获取到的数据进行转换,确保有_original字段
+        const transformedData = Array.isArray(response.data)
+          ? transformPermissionData(response.data)
+          : [];
 
-  // 递归收集所有权限ID
-  const collectAllPermissionIds = (items) => {
-    const ids = [];
-    items.forEach((item) => {
-      ids.push(item.id);
-      if (item.children && item.children.length > 0) {
-        ids.push(...collectAllPermissionIds(item.children));
-      }
-    });
-    return ids;
-  };
+        const findInTree = (items, level = 0) => {
+          const indent = '  '.repeat(level);
 
-  // 清空所有现有权限
-  const clearAllPermissions = async () => {
-    console.log('🗑️ 开始清空现有权限...');
-
-    // 获取现有权限树
-    const response = await admin.auth.access.getTree();
-    if (response.success && response.data && response.data.length > 0) {
-      // 收集所有权限ID
-      const allIds = collectAllPermissionIds(response.data);
-      console.log(`📋 找到 ${allIds.length} 个权限需要删除`);
-
-      // 逐个删除(从子级开始删除,避免外键约束问题)
-      for (let i = allIds.length - 1; i >= 0; i--) {
-        const id = allIds[i];
-        try {
-          const deleteResponse = await admin.auth.access.delete({ id });
-          if (deleteResponse.code === '200') {
-            console.log(`✅ 删除权限成功: ID ${id}`);
-          } else {
-            console.warn(`⚠️ 删除权限失败: ID ${id}`, deleteResponse);
-          }
-        } catch (error) {
-          console.warn(`⚠️ 删除权限异常: ID ${id}`, error);
-        }
+          for (const item of items) {
+            // 现在可以安全使用_original字段了
+            const originalData = item._original || {};
+            const itemName = originalData.name || '';
+            const itemParentId = originalData.parentId || '';
 
-        // 延迟避免请求过快
-        await new Promise((resolve) => setTimeout(resolve, 100));
-      }
+            // 备用匹配:如果原始数据没有name,尝试使用转换后的title
+            const fallbackName = item.title || '';
 
-      console.log('🎯 现有权限清空完成');
-    } else {
-      console.log('📝 没有找到现有权限,跳过清空步骤');
-    }
-  };
+            // 匹配权限名称和父ID(优先使用原始数据的name字段,备用title字段)
+            const nameMatches = itemName === name || (itemName === '' && fallbackName === name);
 
-  // 菜单权限初始化方法
-  const initMenuPermissions = async () => {
-    try {
-      // 确认对话框
-      await ElMessageBox.confirm(
-        '此操作将清空所有现有权限并重新初始化菜单权限,是否继续?\n⚠️ 注意:此操作不可逆!',
-        '初始化确认',
-        {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning',
-          dangerouslyUseHTMLString: true,
-        },
-      );
-
-      initLoading.value = true;
-      console.log('🚀 开始菜单权限初始化...');
-
-      // 1. 首先清空所有现有权限
-      await clearAllPermissions();
-
-      // 数据转换函数
-      const transformMenuData = (item, parentId = '') => ({
-        id: 0,
-        parentId: parentId,
-        name: item.title,
-        eName: item.name,
-        logo: item.icon || '',
-        composingKey: item.name,
-        type: item.type === 'menu' ? 0 : item.type === 'page' ? 1 : 2,
-        status: item.status === 'show' ? 0 : 1,
-        seq: item.weigh || 0,
-        url: '',
-        isAction: item.type === 'api' ? '1' : '0',
-        children: [],
-        createTime: '',
-        createUserId: '',
-        updateTime: '',
-        updateUserId: '',
-      });
+            if (nameMatches) {
+              // 对于顶级权限,parentId可能是空字符串、null、undefined或0
+              const isTopLevel = !parentId || parentId === '' || parentId === '0';
+              const itemIsTopLevel = !itemParentId || itemParentId === '' || itemParentId === '0';
 
-      // 根据权限名称和父ID查找新创建的权限ID
-      const findPermissionId = async (name, parentId = '') => {
-        console.log(`🔍 查找权限: ${name}, 父ID: "${parentId}"`);
-
-        const response = await admin.auth.access.getTree();
-        if (response.success && response.data) {
-          console.log(`🔍 获取到的权限树数据:`, response.data);
-
-          // 🎯 关键修复:对获取到的数据进行转换,确保有_original字段
-          const transformedData = Array.isArray(response.data)
-            ? transformPermissionData(response.data)
-            : [];
-
-          const findInTree = (items, level = 0) => {
-            const indent = '  '.repeat(level);
-
-            for (const item of items) {
-              // 现在可以安全使用_original字段了
-              const originalData = item._original || {};
-              const itemName = originalData.name || '';
-              const itemParentId = originalData.parentId || '';
-
-              // 备用匹配:如果原始数据没有name,尝试使用转换后的title
-              const fallbackName = item.title || '';
-
-              // 匹配权限名称和父ID(优先使用原始数据的name字段,备用title字段)
-              const nameMatches = itemName === name || (itemName === '' && fallbackName === name);
-
-              if (nameMatches) {
-                // 对于顶级权限,parentId可能是空字符串、null、undefined或0
-                const isTopLevel = !parentId || parentId === '' || parentId === '0';
-                const itemIsTopLevel = !itemParentId || itemParentId === '' || itemParentId === '0';
-
-                // 🎯 修复:父ID匹配逻辑
-                const parentIdMatches =
-                  (isTopLevel && itemIsTopLevel) || String(itemParentId) === String(parentId);
-
-                if (parentIdMatches) {
-                  const matchedName = itemName || fallbackName;
-                  return item.id;
-                } else {
-                  console.log(`${indent}❌ 父ID不匹配,跳过此权限`);
-                }
-              }
+              // 🎯 修复:父ID匹配逻辑
+              const parentIdMatches =
+                (isTopLevel && itemIsTopLevel) || String(itemParentId) === String(parentId);
 
-              if (item.children && item.children.length > 0) {
-                const found = findInTree(item.children, level + 1);
-                if (found) return found;
+              if (parentIdMatches) {
+                const matchedName = itemName || fallbackName;
+                return item.id;
+              } else {
+                console.log(`${indent}❌ 父ID不匹配,跳过此权限`);
               }
             }
-            return null;
-          };
 
-          const result = findInTree(transformedData); // 🎯 使用转换后的数据
-          console.log(`🎯 查找结果: ${result ? `找到ID ${result}` : '未找到'}`);
-          return result;
-        }
+            if (item.children && item.children.length > 0) {
+              const found = findInTree(item.children, level + 1);
+              if (found) return found;
+            }
+          }
+          return null;
+        };
 
-        console.log('❌ 获取权限树失败');
-        return null;
-      };
+        const result = findInTree(transformedData); // 🎯 使用转换后的数据
+        console.log(`🎯 查找结果: ${result ? `找到ID ${result}` : '未找到'}`);
+        return result;
+      }
 
-      // 递归创建函数(修改版:处理后端不返回ID的情况)
-      const createRecursive = async (items, parentId = '', level = 0) => {
-        const indent = '  '.repeat(level);
+      console.log('❌ 获取权限树失败');
+      return null;
+    };
 
-        for (let i = 0; i < items.length; i++) {
-          const item = items[i];
+    // 递归创建函数(修改版:处理后端不返回ID的情况)
+    const createRecursive = async (items, parentId = '', level = 0) => {
+      const indent = '  '.repeat(level);
 
-          console.log(`${indent}📝 创建权限: ${item.title} (${item.type}), 父ID: "${parentId}"`);
+      for (let i = 0; i < items.length; i++) {
+        const item = items[i];
 
-          // 创建当前权限
-          const permissionData = transformMenuData(item, parentId);
-          console.log(`${indent}📋 提交数据:`, permissionData);
+        console.log(`${indent}📝 创建权限: ${item.title} (${item.type}), 父ID: "${parentId}"`);
 
-          const response = await admin.auth.access.add(permissionData);
+        // 创建当前权限
+        const permissionData = transformMenuData(item, parentId);
+        console.log(`${indent}📋 提交数据:`, permissionData);
 
-          if (response.code === '200') {
-            console.log(`${indent}✅ 创建成功: ${item.title}`);
+        const response = await admin.auth.access.add(permissionData);
 
-            // 如果有子权限,需要先查找新创建的权限ID
-            if (item.children && item.children.length > 0) {
-              console.log(
-                `${indent}🔍 需要创建 ${item.children.length} 个子权限,正在查找父权限ID...`,
-              );
+        if (response.code === '200') {
+          console.log(`${indent}✅ 创建成功: ${item.title}`);
+
+          // 如果有子权限,需要先查找新创建的权限ID
+          if (item.children && item.children.length > 0) {
+            console.log(
+              `${indent}🔍 需要创建 ${item.children.length} 个子权限,正在查找父权限ID...`,
+            );
 
-              // 等待一下确保数据已保存
-              await new Promise((resolve) => setTimeout(resolve, 500));
+            // 等待一下确保数据已保存
+            await new Promise((resolve) => setTimeout(resolve, 500));
 
-              // 查找新创建的权限ID
-              const newId = await findPermissionId(item.title, parentId);
+            // 查找新创建的权限ID
+            const newId = await findPermissionId(item.title, parentId);
 
-              if (newId) {
-                console.log(`${indent}🎯 找到权限ID: ${newId}`);
-                console.log(`${indent}📁 开始创建子权限...`);
-                await createRecursive(item.children, newId, level + 1);
+            if (newId) {
+              console.log(`${indent}🎯 找到权限ID: ${newId}`);
+              console.log(`${indent}📁 开始创建子权限...`);
+              await createRecursive(item.children, newId, level + 1);
+            } else {
+              console.error(`${indent}❌ 无法找到新创建的权限ID: ${item.title}`);
+              console.error(`${indent}🔍 查找条件: 名称="${item.title}", 父ID="${parentId}"`);
+
+              // 尝试再次查找,增加等待时间
+              console.log(`${indent}⏳ 等待更长时间后重试...`);
+              await new Promise((resolve) => setTimeout(resolve, 1000));
+
+              const retryId = await findPermissionId(item.title, parentId);
+              if (retryId) {
+                console.log(`${indent}🎯 重试成功,找到权限ID: ${retryId}`);
+                await createRecursive(item.children, retryId, level + 1);
               } else {
-                console.error(`${indent}❌ 无法找到新创建的权限ID: ${item.title}`);
-                console.error(`${indent}🔍 查找条件: 名称="${item.title}", 父ID="${parentId}"`);
-
-                // 尝试再次查找,增加等待时间
-                console.log(`${indent}⏳ 等待更长时间后重试...`);
-                await new Promise((resolve) => setTimeout(resolve, 1000));
-
-                const retryId = await findPermissionId(item.title, parentId);
-                if (retryId) {
-                  console.log(`${indent}🎯 重试成功,找到权限ID: ${retryId}`);
-                  await createRecursive(item.children, retryId, level + 1);
-                } else {
-                  throw new Error(`无法找到新创建的权限ID: ${item.title}`);
-                }
+                throw new Error(`无法找到新创建的权限ID: ${item.title}`);
               }
             }
-          } else {
-            console.error(`${indent}❌ 创建失败: ${item.title}`, response);
-            throw new Error(`创建权限失败: ${item.title} - ${response.message || '未知错误'}`);
           }
-
-          // 延迟避免请求过快
-          await new Promise((resolve) => setTimeout(resolve, 300));
+        } else {
+          console.error(`${indent}❌ 创建失败: ${item.title}`, response);
+          throw new Error(`创建权限失败: ${item.title} - ${response.message || '未知错误'}`);
         }
-      };
 
-      // 2. 开始创建新的权限结构
-      console.log('🏗️ 开始创建新的权限结构...');
-      const menuData = menuRulesData.data.menu;
-      await createRecursive(menuData);
+        // 延迟避免请求过快
+        await new Promise((resolve) => setTimeout(resolve, 300));
+      }
+    };
 
-      console.log('🎉 菜单权限初始化完成!');
-      ElMessage.success('菜单权限初始化成功!');
+    // 2. 开始创建新的权限结构
+    console.log('🏗️ 开始创建新的权限结构...');
+    const menuData = menuRulesData.data.menu;
+    await createRecursive(menuData);
 
-      // 3. 重新获取数据刷新界面
-      await getData();
-    } catch (error) {
-      if (error.message !== 'cancel') {
-        console.error('💥 初始化失败:', error);
-        ElMessage.error(`初始化失败: ${error.message}`);
-      }
-    } finally {
-      initLoading.value = false;
+    console.log('🎉 菜单权限初始化完成!');
+    ElMessage.success(t('modules.auth.initSuccess'));
+
+    // 3. 重新获取数据刷新界面
+    await getData();
+  } catch (error) {
+    if (error.message !== 'cancel') {
+      console.error('💥 初始化失败:', error);
+      ElMessage.error(t('modules.auth.initFailed', { message: error.message }));
     }
-  };
+  } finally {
+    initLoading.value = false;
+  }
+};
 
-  onMounted(() => {
-    // 无论是新增还是编辑都需要加载权限数据
-    getData();
-  });
+onMounted(() => {
+  // 无论是新增还是编辑都需要加载权限数据
+  getData();
+});
 </script>
 <style lang="scss" scoped>
-  .sa-access {
-    font-size: 14px;
-    color: var(--sa-subtitle);
-    .el-main {
-      display: flex;
-      transition: all 3s;
+.sa-access {
+  font-size: 14px;
+  color: var(--sa-subtitle);
+
+  .el-main {
+    display: flex;
+    transition: all 3s;
+  }
+
+  .el-scrollbar {
+    flex-shrink: 0;
+    width: 258px;
+    border-right: 1px solid var(--sa-border);
+    border-left: 1px solid var(--sa-border);
+    margin: 0 8px;
+    background: var(--sa-background-assist);
+
+    &:first-of-type {
+      margin-left: 0;
     }
-    .el-scrollbar {
-      flex-shrink: 0;
-      width: 258px;
-      border-right: 1px solid var(--sa-border);
-      border-left: 1px solid var(--sa-border);
-      margin: 0 8px;
-      background: var(--sa-background-assist);
-      &:first-of-type {
-        margin-left: 0;
-      }
-      &:last-of-type {
-        margin-right: 0;
-      }
+
+    &:last-of-type {
+      margin-right: 0;
     }
-    .title {
+  }
+
+  .title {
+    height: 32px;
+    padding: 0 16px;
+    border-bottom: 1px solid var(--sa-border);
+  }
+
+  .node {
+    width: inherit;
+    padding: 0 16px;
+    cursor: pointer;
+
+    .item {
+      flex: 1;
       height: 32px;
-      padding: 0 16px;
-      border-bottom: 1px solid var(--sa-border);
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
     }
-    .node {
-      width: inherit;
-      padding: 0 16px;
-      cursor: pointer;
-      .item {
-        flex: 1;
-        height: 32px;
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-      }
-      .sa-icon {
-        width: 16px !important;
-        height: 16px !important;
-      }
-      .edit {
-        color: var(--el-color-primary);
-      }
-      .delete {
-        color: var(--el-color-danger);
-      }
-      .arrow-right {
-        width: 14px;
-        height: 14px;
-        display: flex;
-        align-items: center;
-      }
-      &.is-active {
-        background: var(--t-bg-active);
-        color: var(--el-color-primary);
-      }
+
+    .sa-icon {
+      width: 16px !important;
+      height: 16px !important;
     }
-    .empty {
-      width: inherit;
-      text-align: center;
-      min-height: 400px;
-      line-height: 400px;
+
+    .edit {
+      color: var(--el-color-primary);
     }
+
+    .delete {
+      color: var(--el-color-danger);
+    }
+
+    .arrow-right {
+      width: 14px;
+      height: 14px;
+      display: flex;
+      align-items: center;
+    }
+
+    &.is-active {
+      background: var(--t-bg-active);
+      color: var(--el-color-primary);
+    }
+  }
+
+  .empty {
+    width: inherit;
+    text-align: center;
+    min-height: 400px;
+    line-height: 400px;
   }
+}
 </style>

+ 207 - 226
src/app/admin/views/auth/access/edit.vue

@@ -1,13 +1,7 @@
 <template>
   <el-container>
     <el-main>
-      <el-form
-        v-loading="loading"
-        :model="form.model"
-        :rules="form.rules"
-        ref="formRef"
-        label-width="100px"
-      >
+      <el-form v-loading="loading" :model="form.model" :rules="form.rules" ref="formRef" :label-width="formLabelWidth">
         <!-- <el-form-item label="父级" prop="parent_id">
           <el-cascader
             ref="parentCascader"
@@ -24,283 +18,270 @@
             disabled
           ></el-cascader>
         </el-form-item> -->
-        <el-form-item label="类型" prop="type">
+        <el-form-item :label="t('modules.auth.permissionType')" prop="type">
           <el-radio-group v-model="form.model.type" @change="changeType">
-            <el-radio label="1">菜单</el-radio>
-            <el-radio label="2">页面</el-radio>
-            <el-radio label="4">动作</el-radio>
+            <el-radio label="1">{{ t('menu.menu') }}</el-radio>
+            <el-radio label="2">{{ t('menu.page') }}</el-radio>
+            <el-radio label="4">{{ t('menu.action') }}</el-radio>
             <!-- <el-radio label="3">页面</el-radio> -->
           </el-radio-group>
         </el-form-item>
-        <el-form-item label="标题" prop="title">
-          <el-input v-model="form.model.title"></el-input>
+        <el-form-item :label="t('common.title')" prop="title">
+          <el-input v-model="form.model.title" :placeholder="t('common.enterName')"></el-input>
         </el-form-item>
-        <el-form-item label="标识" prop="name">
-          <el-input v-model="form.model.name" placeholder="请输入唯一标识"></el-input>
+        <el-form-item :label="t('common.identifier')" prop="name">
+          <el-input v-model="form.model.name" :placeholder="t('common.enterUniqueIdentifier')"></el-input>
         </el-form-item>
-        <el-form-item label="短路径" prop="url">
-          <el-input v-model="form.model.url" placeholder="请输入短路径"></el-input>
+        <el-form-item :label="t('modules.auth.url')" prop="url">
+          <el-input v-model="form.model.url" :placeholder="t('common.enterShortPath')"></el-input>
         </el-form-item>
-        <el-form-item v-if="form.model.type != 'api'" label="图标" prop="icon">
-          <sa-uploader v-model="form.model.icon" mode="input" fileType="image"></sa-uploader>
+        <el-form-item v-if="form.model.type != 'api'" :label="t('modules.auth.icon')" prop="icon">
+          <sa-uploader v-model="form.model.icon" mode="input" fileType="image"
+            :text="t('common.selectIcon')"></sa-uploader>
         </el-form-item>
         <!-- <el-form-item v-if="form.model.type != 'api'" label="跳转地址" prop="url">
           <el-input v-model="form.model.url" placeholder="请输入跳转地址"></el-input>
         </el-form-item> -->
-        <el-form-item label="权重" prop="weigh">
-          <el-input v-model="form.model.weigh" placeholder="请输入权重" type="number"></el-input>
+        <el-form-item :label="t('common.weight')" prop="weigh">
+          <el-input v-model="form.model.weigh" :placeholder="t('common.enterWeight')" type="number"></el-input>
         </el-form-item>
-        <el-form-item label="状态" prop="status">
+        <el-form-item :label="t('common.status')" prop="status">
           <div>
             <el-radio-group v-model="form.model.status">
-              <el-radio label="normal">正常</el-radio>
-              <el-radio label="disabled">禁用</el-radio>
+              <el-radio label="normal">{{ t('common.normal') }}</el-radio>
+              <el-radio label="disabled">{{ t('common.disabled') }}</el-radio>
             </el-radio-group>
             <div class="tip">
-              <div>正常:该权限正常可用</div>
-              <div>禁用:禁用该权限后,除了超级管理员都将无法访问此权限</div>
+              <div>{{ t('common.normal') }}:{{ t('modules.auth.normalTip') }}</div>
+              <div>{{ t('common.disabled') }}:{{ t('modules.auth.disabledTip') }}</div>
             </div>
           </div>
         </el-form-item>
-        <el-form-item v-if="form.model.type == 'page' && !modal.params.id" label="包含权限">
+        <el-form-item v-if="form.model.type == 'page' && !modal.params.id" :label="t('common.includePermissions')">
           <div class="sa-template-wrap">
             <div class="title sa-flex">
-              <div class="key">名称</div>
-              <div class="value">标识</div>
-              <div class="oper">操作</div>
+              <div class="key">{{ t('common.name') }}</div>
+              <div class="value">{{ t('common.identifier') }}</div>
+              <div class="oper">{{ t('common.operation') }}</div>
             </div>
-            <sa-draggable
-              v-model="form.model.api"
-              :animation="300"
-              handle=".sortable-drag"
-              item-key="element"
-            >
+            <sa-draggable v-model="form.model.api" :animation="300" handle=".sortable-drag" item-key="element">
               <template #item="{ element, index }">
                 <div class="item">
-                  <el-form-item
-                    class="key"
-                    :prop="'api.' + index + '.title'"
-                    :rules="templateRules.title"
-                  >
-                    <el-input placeholder="请输入名称" v-model="element.title"></el-input>
+                  <el-form-item class="key" :prop="'api.' + index + '.title'" :rules="templateRules.title">
+                    <el-input :placeholder="t('common.enterName')" v-model="element.title"></el-input>
                   </el-form-item>
-                  <el-form-item
-                    class="value"
-                    :prop="'api.' + index + '.name'"
-                    :rules="templateRules.name"
-                  >
-                    <el-input placeholder="请输入标识" v-model="element.name"></el-input>
+                  <el-form-item class="value" :prop="'api.' + index + '.name'" :rules="templateRules.name">
+                    <el-input :placeholder="t('common.enterIdentifier')" v-model="element.name"></el-input>
                   </el-form-item>
                   <el-form-item class="oper">
-                    <el-button @click="deleteTemplate(index)" type="danger" plain>删除</el-button>
+                    <el-button @click="deleteTemplate(index)" type="danger" plain>{{ t('common.delete') }}</el-button>
                     <sa-svg class="sa-m-l-8 sortable-drag" name="sa-round"></sa-svg>
                   </el-form-item>
                 </div>
               </template>
             </sa-draggable>
-            <el-button @click="addTemplate()" class="sa-m-l-16" type="primary" plain icon="Plus"
-              >添加</el-button
-            >
-            <el-button
-              v-if="form.model.api.length == 0"
-              @click="addCurd()"
-              class="sa-m-l-16"
-              type="success"
-              plain
-              icon="Plus"
-              >CRUD</el-button
-            >
+            <el-button @click="addTemplate()" class="sa-m-l-16" type="primary" plain icon="Plus">{{ t('common.add')
+            }}</el-button>
+            <el-button v-if="form.model.api.length == 0" @click="addCurd()" class="sa-m-l-16" type="success" plain
+              icon="Plus">CRUD</el-button>
           </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 { onMounted, reactive, ref, unref } from 'vue';
-  import { doUnique } from '@/sheep/utils/index.js';
-  import admin from '@/app/admin/api';
-  import SaDraggable from 'vuedraggable';
-  import { cloneDeep } from 'lodash';
-  import { ElMessage } from 'element-plus';
+import { onMounted, reactive, ref, unref } from 'vue';
+import { doUnique } from '@/sheep/utils/index.js';
+import admin from '@/app/admin/api';
+import SaDraggable from 'vuedraggable';
+import { cloneDeep } from 'lodash';
+import { useI18n } from 'vue-i18n';
+import { useFormConfig } from '@/hooks/useFormConfig';
+import { ElMessage } from 'element-plus';
 
-  const apiCrud = [
-    {
-      title: '查看',
-      name: 'list',
-    },
-    {
-      title: '详情',
-      name: 'detail',
-    },
-    {
-      title: '新建',
-      name: 'add',
-    },
-    {
-      title: '编辑',
-      name: 'edit',
-    },
-    {
-      title: '删除',
-      name: 'delete',
-    },
-  ];
+const { t } = useI18n();
 
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
+// 使用表单配置hooks
+const { formLabelWidth } = useFormConfig({ enWidth: '140px' });
 
-  // 添加 编辑 form
-  let formRef = ref(null);
-  const form = reactive({
-    model: {
-      parent_id: props.modal.params.parent_id || '',
-      title: '',
-      type: '1',
-      name: '',
-      eName: '',
-      icon: '',
-      url: '',
-      status: 'normal',
-      weigh: '',
-      api: [],
-    },
-    rules: {
-      title: [{ required: true, message: '请输入名称', trigger: 'blur' }],
-      name: [{ required: true, message: '请输入标识', trigger: 'blur' }],
-    },
-  });
-  // api 表单规则
-  const templateRef = ref(null);
-  const templateRules = {
-    title: [{ required: true, message: '请输入名称', trigger: 'blur' }],
-    name: [{ required: true, message: '请输入标识', trigger: 'blur' }],
-  };
+const apiCrud = [
+  {
+    title: '查看',
+    name: 'list',
+  },
+  {
+    title: '详情',
+    name: 'detail',
+  },
+  {
+    title: '新建',
+    name: 'add',
+  },
+  {
+    title: '编辑',
+    name: 'edit',
+  },
+  {
+    title: '删除',
+    name: 'delete',
+  },
+];
 
-  const loading = ref(false);
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps({
+  modal: {
+    type: Object,
+  },
+});
 
-  // 数据转换函数:将新接口数据转换为表单需要的格式
-  function transformDetailData(data) {
-    return {
-      parent_id: data.parentId || null,
-      title: data.name || '', // 使用name作为title
-      type: data.type, // 转换type格式
-      name: data.composingKey || '', // 使用composingKey作为标识
-      icon: data.logo || '',
-      url: data.url || '', // 跳转地址
-      status: getFormStatus(data.status), // 转换status格式
-      weigh: data.seq || 0, // 使用seq作为权重
-      api: [], // API权限暂时为空,可根据需要扩展
-    };
-  }
+// 添加 编辑 form
+let formRef = ref(null);
+const form = reactive({
+  model: {
+    parent_id: props.modal.params.parent_id || '',
+    title: '',
+    type: '1',
+    name: '',
+    eName: '',
+    icon: '',
+    url: '',
+    status: 'normal',
+    weigh: '',
+    api: [],
+  },
+  rules: {
+    title: [{ required: true, message: t('common.enterName'), trigger: 'blur' }],
+    name: [{ required: true, message: t('common.enterIdentifier'), trigger: 'blur' }],
+  },
+});
+// api 表单规则
+const templateRef = ref(null);
+const templateRules = {
+  title: [{ required: true, message: t('common.enterName'), trigger: 'blur' }],
+  name: [{ required: true, message: t('common.enterIdentifier'), trigger: 'blur' }],
+};
 
-  // 转换status字段:数字转换为字符串
-  function getFormStatus(status) {
-    const statusMap = {
-      0: 'normal', // 正常
-      1: 'disabled', // 禁用
-    };
-    return statusMap[String(status)] || 'normal';
-  }
+const loading = ref(false);
 
-  // 获取详情
-  async function getDetail(id) {
-    loading.value = true;
-    try {
-      const response = await admin.auth.access.detail(id);
+// 数据转换函数:将新接口数据转换为表单需要的格式
+function transformDetailData(data) {
+  return {
+    parent_id: data.parentId || null,
+    title: data.name || '', // 使用name作为title
+    type: data.type, // 转换type格式
+    name: data.composingKey || '', // 使用composingKey作为标识
+    icon: data.logo || '',
+    url: data.url || '', // 跳转地址
+    status: getFormStatus(data.status), // 转换status格式
+    weigh: data.seq || 0, // 使用seq作为权重
+    api: [], // API权限暂时为空,可根据需要扩展
+  };
+}
 
-      if (response.success && response.data) {
-        // 转换数据格式
-        form.model = transformDetailData(response.data);
-      } else {
-        console.error('获取权限详情失败:', response.message);
-      }
-    } catch (error) {
-      console.error('获取权限详情异常:', error);
-    }
+// 转换status字段:数字转换为字符串
+function getFormStatus(status) {
+  const statusMap = {
+    0: 'normal', // 正常
+    1: 'disabled', // 禁用
+  };
+  return statusMap[String(status)] || 'normal';
+}
 
-    loading.value = false;
-  }
-  function changeType(e) {
-    if (e != 'page') form.model.api = [];
-  }
+// 获取详情
+async function getDetail(id) {
+  loading.value = true;
+  try {
+    const response = await admin.auth.access.detail(id);
 
-  // 添加权限
-  function addTemplate() {
-    form.model.api.push({
-      title: '',
-      name: '',
-    });
-  }
-  function addCurd() {
-    form.model.api = [].concat(form.model.api, apiCrud);
-  }
-  function deleteTemplate(index) {
-    form.model.api.splice(index, 1);
+    if (response.success && response.data) {
+      // 转换数据格式
+      form.model = transformDetailData(response.data);
+    } else {
+      console.error('获取权限详情失败:', response.message);
+    }
+  } catch (error) {
+    console.error('获取权限详情异常:', error);
   }
 
-  // 转换提交数据:将表单数据转换为后端需要的格式
-  function transformSubmitData(formData) {
-    const isAdd = props.modal.params.type == 'add' || props.modal.params.type == 'append';
+  loading.value = false;
+}
+function changeType(e) {
+  if (e != 'page') form.model.api = [];
+}
 
-    return {
-      id: isAdd ? 0 : props.modal.params.id || 0, // 新增时ID为0,编辑时传递原ID
-      parentId: formData.parent_id || '',
-      name: formData.title || '', // 使用title作为name
-      eName: formData.eName || '', // 使用title作为eName
-      logo: formData.icon || '',
-      composingKey: formData.name || '', // 使用name作为composingKey
-      type: formData.type, // 转换type格式
-      status: getSubmitStatus(formData.status), // 转换status格式
-      seq: formData.weigh || 0, // 使用weigh作为seq
-      url: formData.url || '', // 跳转地址
-      isAction: formData.type === 'api' ? '1' : '0', // 是否为动作
-      children: [], // 子节点为空数组
-      createTime: '', // 创建时间由后端处理
-      createUserId: '', // 创建用户由后端处理
-      updateTime: '', // 更新时间由后端处理
-      updateUserId: '', // 更新用户由后端处理
-    };
-  }
+// 添加权限
+function addTemplate() {
+  form.model.api.push({
+    title: '',
+    name: '',
+  });
+}
+function addCurd() {
+  form.model.api = [].concat(form.model.api, apiCrud);
+}
+function deleteTemplate(index) {
+  form.model.api.splice(index, 1);
+}
 
-  // 转换status字段:字符串转换为数字
-  function getSubmitStatus(status) {
-    const statusMap = {
-      normal: 0, // 正常
-      disabled: 1, // 禁用
-    };
-    return statusMap[status] || 0;
-  }
+// 转换提交数据:将表单数据转换为后端需要的格式
+function transformSubmitData(formData) {
+  const isAdd = props.modal.params.type == 'add' || props.modal.params.type == 'append';
 
-  // 表单关闭时提交
-  function confirm() {
-    // 表单验证
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
+  return {
+    id: isAdd ? 0 : props.modal.params.id || 0, // 新增时ID为0,编辑时传递原ID
+    parentId: formData.parent_id || '',
+    name: formData.title || '', // 使用title作为name
+    eName: formData.eName || '', // 使用title作为eName
+    logo: formData.icon || '',
+    composingKey: formData.name || '', // 使用name作为composingKey
+    type: formData.type, // 转换type格式
+    status: getSubmitStatus(formData.status), // 转换status格式
+    seq: formData.weigh || 0, // 使用weigh作为seq
+    url: formData.url || '', // 跳转地址
+    isAction: formData.type === 'api' ? '1' : '0', // 是否为动作
+    children: [], // 子节点为空数组
+    createTime: '', // 创建时间由后端处理
+    createUserId: '', // 创建用户由后端处理
+    updateTime: '', // 更新时间由后端处理
+    updateUserId: '', // 更新用户由后端处理
+  };
+}
 
-      // 转换提交数据格式
-      let submitForm = transformSubmitData(form.model);
+// 转换status字段:字符串转换为数字
+function getSubmitStatus(status) {
+  const statusMap = {
+    normal: 0, // 正常
+    disabled: 1, // 禁用
+  };
+  return statusMap[status] || 0;
+}
 
-      const { code, data } =
-        props.modal.params.type == 'add' || props.modal.params.type == 'append'
-          ? await admin.auth.access.add(submitForm)
-          : await admin.auth.access.edit(submitForm);
-      ElMessage.success(props.modal.params.type == 'add' ? '创建成功' : '更新成功');
-      code == '200' && emit('modalCallBack', { event: 'confirm', data });
-    });
-  }
+// 表单关闭时提交
+function confirm() {
+  // 表单验证
+  unref(formRef).validate(async (valid) => {
+    if (!valid) return;
 
-  onMounted(async () => {
-    if (props.modal.params.type == 'edit') {
-      await getDetail(props.modal.params.id);
-    }
+    // 转换提交数据格式
+    let submitForm = transformSubmitData(form.model);
+
+    const { code, data } =
+      props.modal.params.type == 'add' || props.modal.params.type == 'append'
+        ? await admin.auth.access.add(submitForm)
+        : await admin.auth.access.edit(submitForm);
+    ElMessage.success(props.modal.params.type == 'add' ? t('message.createSuccess') : t('message.updateSuccess'));
+    code == '200' && emit('modalCallBack', { event: 'confirm', data });
   });
+}
+
+onMounted(async () => {
+  if (props.modal.params.type == 'edit') {
+    await getDetail(props.modal.params.id);
+  }
+});
 </script>

+ 141 - 151
src/app/admin/views/auth/admin/edit.vue

@@ -1,188 +1,178 @@
 <template>
   <el-container>
     <el-main>
-      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="100px">
-        <el-form-item label="用户名" prop="account">
-          <el-input v-model="form.model.account" placeholder="请输入用户名"></el-input>
+      <el-form :model="form.model" :rules="form.rules" ref="formRef" :label-width="formLabelWidth">
+        <el-form-item :label="t('modules.auth.username')" prop="account">
+          <el-input v-model="form.model.account" :placeholder="t('modules.auth.usernameRequired')"></el-input>
         </el-form-item>
-        <el-form-item label="用户角色" prop="roleIds">
-          <el-select
-            v-model="form.model.roleIds"
-            placeholder="请选择用户角色"
-            multiple
-            clearable
-            style="width: 100%"
-          >
-            <el-option
-              v-for="item in role.list"
-              :key="item.id"
-              :label="item.name"
-              :value="item.id"
-            ></el-option>
+        <el-form-item :label="t('modules.auth.adminRole')" prop="roleIds">
+          <el-select v-model="form.model.roleIds" :placeholder="t('modules.auth.roleRequired')" multiple clearable
+            style="width: 100%">
+            <el-option v-for="item in role.list" :key="item.id" :label="item.name" :value="item.id"></el-option>
           </el-select>
         </el-form-item>
-        <el-form-item v-if="modal.params.type === 'add'" label="密码" prop="pwd">
-          <el-input
-            v-model="form.model.pwd"
-            type="password"
-            placeholder="请输入密码"
-            show-password
-          ></el-input>
+        <el-form-item v-if="modal.params.type === 'add'" :label="t('modules.auth.password')" prop="pwd">
+          <el-input v-model="form.model.pwd" type="password" :placeholder="t('modules.auth.passwordRequired')"
+            show-password></el-input>
         </el-form-item>
-        <el-form-item v-if="modal.params.type === 'add'" label="确认密码" prop="confirmPassword">
-          <el-input
-            v-model="form.model.confirmPassword"
-            type="password"
-            placeholder="请再次输入密码"
-            show-password
-          ></el-input>
+        <el-form-item v-if="modal.params.type === 'add'" :label="t('modules.auth.confirmPassword')"
+          prop="confirmPassword">
+          <el-input v-model="form.model.confirmPassword" type="password"
+            :placeholder="t('modules.auth.confirmPasswordRequired')" show-password></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 { onMounted, reactive, ref, unref } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import admin from '@/app/admin/api';
+import { onMounted, reactive, ref, unref } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import { useFormConfig } from '@/hooks/useFormConfig';
+import admin from '@/app/admin/api';
 
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
+const { t } = useI18n();
+
+// 使用表单配置hooks
+const { formLabelWidth } = useFormConfig({ enWidth: '150px' });
+
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps({
+  modal: {
+    type: Object,
+  },
+});
 
-  // 添加 编辑 form
-  let formRef = ref(null);
-  const form = reactive({
-    model: {
-      id: null,
-      account: '',
-      roleIds: [],
-      pwd: '',
-      confirmPassword: '',
-    },
-    rules: {
-      account: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
-      roleIds: [{ required: true, message: '请选择用户角色', trigger: 'change' }],
-      pwd: [
-        { required: true, message: '请输入密码', trigger: 'blur' },
-        { min: 6, message: '密码长度不能少于6位', trigger: 'blur' },
-      ],
-      confirmPassword: [
-        { required: true, message: '请再次输入密码', trigger: 'blur' },
-        {
-          validator: (_, value, callback) => {
-            if (value !== form.model.pwd) {
-              callback(new Error('两次输入的密码不一致'));
-            } else {
-              callback();
-            }
-          },
-          trigger: 'blur',
+// 添加 编辑 form
+let formRef = ref(null);
+const form = reactive({
+  model: {
+    id: null,
+    account: '',
+    roleIds: [],
+    pwd: '',
+    confirmPassword: '',
+  },
+  rules: {
+    account: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
+    roleIds: [{ required: true, message: '请选择用户角色', trigger: 'change' }],
+    pwd: [
+      { required: true, message: '请输入密码', trigger: 'blur' },
+      { min: 6, message: '密码长度不能少于6位', trigger: 'blur' },
+    ],
+    confirmPassword: [
+      { required: true, message: '请再次输入密码', trigger: 'blur' },
+      {
+        validator: (_, value, callback) => {
+          if (value !== form.model.pwd) {
+            callback(new Error('两次输入的密码不一致'));
+          } else {
+            callback();
+          }
         },
-      ],
-    },
-  });
-  const loading = ref(false);
+        trigger: 'blur',
+      },
+    ],
+  },
+});
+const loading = ref(false);
 
-  // 角色列表
-  const role = reactive({
-    list: [],
-  });
+// 角色列表
+const role = reactive({
+  list: [],
+});
 
-  // 获取角色列表
-  async function getRoleList() {
-    try {
-      const { code, data } = await admin.auth.role.roleList({
-        page: 1,
-        size: 100, // 获取所有角色
-      });
-      if (code == 200) {
-        role.list = data.list || [];
-      }
-    } catch (error) {
-      console.error('获取角色列表失败:', error);
-      ElMessage.error('获取角色列表失败');
+// 获取角色列表
+async function getRoleList() {
+  try {
+    const { code, data } = await admin.auth.role.roleList({
+      page: 1,
+      size: 100, // 获取所有角色
+    });
+    if (code == 200) {
+      role.list = data.list || [];
     }
+  } catch (error) {
+    console.error('获取角色列表失败:', error);
+    ElMessage.error(t('message.fetchRoleListFailed'));
   }
+}
 
-  // 获取详情
-  async function getDetail(id) {
-    loading.value = true;
-    try {
-      const { code, data } = await admin.auth.user.detail(id);
-      if (code == 200) {
-        form.model.id = data.id;
-        form.model.account = data.account || data.name;
-        // 处理角色数据:从userRoles数组中提取roleId
-        if (data.userRoles && Array.isArray(data.userRoles)) {
-          form.model.roleIds = data.userRoles.map((item) => item.roleId);
-        } else if (data.roleIds) {
-          // 兼容旧的roleIds字段格式
-          form.model.roleIds =
-            typeof data.roleIds === 'string'
-              ? data.roleIds.split(',').map((id) => parseInt(id))
-              : data.roleIds;
-        } else {
-          form.model.roleIds = [];
-        }
+// 获取详情
+async function getDetail(id) {
+  loading.value = true;
+  try {
+    const { code, data } = await admin.auth.user.detail(id);
+    if (code == 200) {
+      form.model.id = data.id;
+      form.model.account = data.account || data.name;
+      // 处理角色数据:从userRoles数组中提取roleId
+      if (data.userRoles && Array.isArray(data.userRoles)) {
+        form.model.roleIds = data.userRoles.map((item) => item.roleId);
+      } else if (data.roleIds) {
+        // 兼容旧的roleIds字段格式
+        form.model.roleIds =
+          typeof data.roleIds === 'string'
+            ? data.roleIds.split(',').map((id) => parseInt(id))
+            : data.roleIds;
+      } else {
+        form.model.roleIds = [];
       }
-    } catch (error) {
-      console.error('获取详情失败:', error);
-      ElMessage.error('获取详情失败');
     }
-    loading.value = false;
+  } catch (error) {
+    console.error('获取详情失败:', error);
+    ElMessage.error(t('message.fetchDetailFailed'));
   }
+  loading.value = false;
+}
 
-  // 表单关闭时提交
-  function confirm() {
-    // 表单验证
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
+// 表单关闭时提交
+function confirm() {
+  // 表单验证
+  unref(formRef).validate(async (valid) => {
+    if (!valid) return;
 
-      try {
-        let submitData = {};
+    try {
+      let submitData = {};
 
-        const isAdd = props.modal.params.type === 'add';
+      const isAdd = props.modal.params.type === 'add';
 
-        // 构建提交数据
-        submitData = {
-          account: form.model.account,
-          roleIds: form.model.roleIds.join(','),
-          ...(isAdd && { pwd: form.model.pwd }), // 新增时才包含密码
-          id: isAdd ? '' : props.modal.params.id, // 新增时id为空字符串,编辑时传递id
-        };
+      // 构建提交数据
+      submitData = {
+        account: form.model.account,
+        roleIds: form.model.roleIds.join(','),
+        ...(isAdd && { pwd: form.model.pwd }), // 新增时才包含密码
+        id: isAdd ? '' : props.modal.params.id, // 新增时id为空字符串,编辑时传递id
+      };
 
-        // 调用对应接口
-        const { code } = isAdd
-          ? await admin.auth.user.add(submitData)
-          : await admin.auth.user.edit(submitData);
+      // 调用对应接口
+      const { code } = isAdd
+        ? await admin.auth.user.add(submitData)
+        : await admin.auth.user.edit(submitData);
 
-        if (code == 200) {
-          ElMessage.success(isAdd ? '创建成功' : '更新成功');
-          emit('modalCallBack', { event: 'confirm' });
-        }
-      } catch (error) {
-        console.error('提交失败:', error);
-        ElMessage.error('提交失败');
+      if (code == 200) {
+        ElMessage.success(isAdd ? t('message.createSuccess') : t('message.updateSuccess'));
+        emit('modalCallBack', { event: 'confirm' });
       }
-    });
-  }
-
-  async function init() {
-    await getRoleList(); // 获取角色列表
-    if (props.modal.params.id) {
-      await getDetail(props.modal.params.id);
+    } catch (error) {
+      console.error('提交失败:', error);
+      ElMessage.error(t('message.submitFailed'));
     }
+  });
+}
+
+async function init() {
+  await getRoleList(); // 获取角色列表
+  if (props.modal.params.id) {
+    await getDetail(props.modal.params.id);
   }
+}
 
-  onMounted(() => {
-    init();
-  });
+onMounted(() => {
+  init();
+});
 </script>

+ 148 - 164
src/app/admin/views/auth/admin/index.vue

@@ -13,14 +13,7 @@
     </el-header>
     <el-main class="sa-p-0">
       <div class="sa-table-wrap panel-block panel-block--bottom" v-loading="loading">
-        <el-table
-          height="100%"
-          class="sa-table"
-          :data="table.data"
-          @sort-change="fieldFilter"
-          row-key="id"
-          stripe
-        >
+        <el-table height="100%" class="sa-table" :data="table.data" @sort-change="fieldFilter" row-key="id" stripe>
           <template #empty>
             <sa-empty />
           </template>
@@ -34,60 +27,50 @@
           <el-table-column :label="t('form.status')" min-width="120" align="center">
             <template #default="scope">
               <!-- ID为1的用户状态不可修改 -->
-              <el-tag
-                v-if="scope.row.id == 1"
-                :type="scope.row.status === '0' ? 'success' : 'danger'"
-              >
-                <span>{{ scope.row.status === '0' ? '启用' : '禁用' }}</span>
+              <el-tag v-if="scope.row.id == 1" :type="scope.row.status === '0' ? 'success' : 'danger'">
+                <span>{{ scope.row.status === '0' ? t('common.enabled') : t('common.disabled') }}</span>
               </el-tag>
               <!-- 其他用户可以修改状态 -->
               <el-dropdown v-else popper-class="user-dropdown" @command="handleStatusCommand">
                 <el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
-                  <span>{{ scope.row.status === '0' ? '启用' : '禁用' }}</span>
+                  <span>{{ scope.row.status === '0' ? t('common.enabled') : t('common.disabled') }}</span>
                   <el-icon class="sa-m-l-4">
                     <ArrowDown />
                   </el-icon>
                 </el-tag>
                 <template #dropdown>
                   <el-dropdown-menu>
-                    <el-dropdown-item
-                      :command="{
-                        id: scope.row.id,
-                        type: 'enable',
-                      }"
-                    >
-                      <span class="status-up">启用</span>
+                    <el-dropdown-item :command="{
+                      id: scope.row.id,
+                      type: 'enable',
+                    }">
+                      <span class="status-up">{{ t('common.enable') }}</span>
                     </el-dropdown-item>
-                    <el-dropdown-item
-                      :command="{
-                        id: scope.row.id,
-                        type: 'disable',
-                      }"
-                    >
-                      <span class="status-down">禁用</span>
+                    <el-dropdown-item :command="{
+                      id: scope.row.id,
+                      type: 'disable',
+                    }">
+                      <span class="status-down">{{ t('common.disable') }}</span>
                     </el-dropdown-item>
                   </el-dropdown-menu>
                 </template>
               </el-dropdown>
             </template>
           </el-table-column>
-          <el-table-column fixed="right" label="操作">
+          <el-table-column fixed="right" :label="t('common.actions')">
             <template #default="scope">
               <!-- 修改密码按钮:所有用户都可以 -->
-              <el-button link type="warning" @click="changePassword(scope.row)">修改密码</el-button>
+              <el-button link type="warning" @click="changePassword(scope.row)">{{ t('modules.auth.changePassword')
+              }}</el-button>
 
               <!-- ID为1的用户不能编辑和删除 -->
               <template v-if="scope.row.id != 1">
-                <el-button link type="primary" @click="editRow(scope.row)">编辑</el-button>
-                <el-popconfirm
-                  width="fit-content"
-                  confirm-button-text="确认"
-                  cancel-button-text="取消"
-                  title="确认删除这条记录?"
-                  @confirm="deleteApi(scope.row.id)"
-                >
+                <el-button link type="primary" @click="editRow(scope.row)">{{ t('common.edit') }}</el-button>
+                <el-popconfirm width="fit-content" :confirm-button-text="t('common.confirm')"
+                  :cancel-button-text="t('common.cancel')" :title="t('message.confirmDelete')"
+                  @confirm="deleteApi(scope.row.id)">
                   <template #reference>
-                    <el-button link type="danger">删除</el-button>
+                    <el-button link type="danger">{{ t('common.delete') }}</el-button>
                   </template>
                 </el-popconfirm>
               </template>
@@ -104,150 +87,151 @@
   </el-container>
 </template>
 <script>
-  export default {
-    name: 'admin.auth.admin',
-  };
+export default {
+  name: 'admin.auth.admin',
+};
 </script>
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import { ArrowDown } from '@element-plus/icons-vue';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import { useI18n } from 'vue-i18n';
-  import admin from '@/app/admin/api';
-
-  const { t } = useI18n();
-  import AdminEdit from './edit.vue';
-  import PasswordEdit from './password.vue';
-
-  const { pageData } = usePagination();
-
-  // 表格状态
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-  });
-  const loading = ref(true);
-
-  // 获取数据
-  async function getData(page, searchParams = {}) {
-    if (page) pageData.page = page;
-    loading.value = true;
-
-    try {
-      const { code, data } = await admin.auth.user.userList({
-        page: pageData.page,
-        size: pageData.size,
-        order: table.order,
-        sort: table.sort,
-        ...searchParams,
-      });
-
-      if (code == 200) {
-        table.data = data.list;
-        pageData.page = data.pageNum;
-        pageData.size = data.pageSize;
-        pageData.total = data.total;
-      }
-    } catch (error) {
-      console.error('获取数据失败:', error);
-    } finally {
-      loading.value = false;
+import { onMounted, reactive, ref } from 'vue';
+import { ElMessage } from 'element-plus';
+import { ArrowDown } from '@element-plus/icons-vue';
+import { useModal, usePagination } from '@/sheep/hooks';
+import { useI18n } from 'vue-i18n';
+import admin from '@/app/admin/api';
+
+const { t } = useI18n();
+import AdminEdit from './edit.vue';
+import PasswordEdit from './password.vue';
+
+const { pageData } = usePagination();
+
+// 表格状态
+const table = reactive({
+  data: [],
+  order: '',
+  sort: '',
+});
+const loading = ref(true);
+
+// 获取数据
+async function getData(page, searchParams = {}) {
+  if (page) pageData.page = page;
+  loading.value = true;
+
+  try {
+    const { code, data } = await admin.auth.user.userList({
+      page: pageData.page,
+      size: pageData.size,
+      order: table.order,
+      sort: table.sort,
+      ...searchParams,
+    });
+
+    if (code == 200) {
+      table.data = data.list;
+      pageData.page = data.pageNum;
+      pageData.size = data.pageSize;
+      pageData.total = data.total;
     }
+  } catch (error) {
+    console.error('获取数据失败:', error);
+  } finally {
+    loading.value = false;
   }
+}
 
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
+// table 字段排序
+function fieldFilter({ prop, order }) {
+  table.order = order == 'ascending' ? 'asc' : 'desc';
+  table.sort = prop;
+  getData();
+}
 
-  // 新增人员
-  function addRow() {
-    useModal(
-      AdminEdit,
-      { title: '新增人员', type: 'add' },
-      {
-        confirm: () => {
-          getData();
-        },
+// 新增人员
+function addRow() {
+  useModal(
+    AdminEdit,
+    { title: '新增人员', type: 'add' },
+    {
+      confirm: () => {
+        getData();
       },
-    );
-  }
+    },
+  );
+}
 
-  // 编辑人员
-  function editRow(row) {
-    useModal(
-      AdminEdit,
-      {
-        title: '编辑人员',
-        type: 'edit',
-        id: row.id,
+// 编辑人员
+function editRow(row) {
+  useModal(
+    AdminEdit,
+    {
+      title: '编辑人员',
+      type: 'edit',
+      id: row.id,
+    },
+    {
+      confirm: () => {
+        getData();
       },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
+    },
+  );
+}
 
-  // 修改密码
-  function changePassword(row) {
-    useModal(
-      PasswordEdit,
-      {
-        title: '修改密码',
-        type: 'password',
-        id: row.id,
-        name: row.name,
+// 修改密码
+function changePassword(row) {
+  useModal(
+    PasswordEdit,
+    {
+      title: '修改密码',
+      type: 'password',
+      id: row.id,
+      name: row.name,
+    },
+    {
+      confirm: () => {
+        ElMessage.success('密码修改成功');
       },
-      {
-        confirm: () => {
-          ElMessage.success('密码修改成功');
-        },
-      },
-    );
-  }
-
-  // 状态切换处理
-  async function handleStatusCommand(command) {
-    try {
-      await admin.auth.user.status(command.id);
-      ElMessage.success('状态切换成功');
-      getData();
-    } catch (error) {
-      console.error('状态切换失败:', error);
-      ElMessage.error('状态切换失败');
-    }
-  }
+    },
+  );
+}
 
-  // 删除人员
-  async function deleteApi(id) {
-    await admin.auth.user.delete({ id });
+// 状态切换处理
+async function handleStatusCommand(command) {
+  try {
+    await admin.auth.user.status(command.id);
+    ElMessage.success('状态切换成功');
     getData();
+  } catch (error) {
+    console.error('状态切换失败:', error);
+    ElMessage.error('状态切换失败');
   }
+}
+
+// 删除人员
+async function deleteApi(id) {
+  await admin.auth.user.delete({ id });
+  getData();
+}
 
-  onMounted(async () => {
-    await getData();
-  });
+onMounted(async () => {
+  await getData();
+});
 </script>
 <style lang="scss" scoped>
-  .login-time {
-    font-size: 12px;
-    color: var(--sa-font);
-  }
+.login-time {
+  font-size: 12px;
+  color: var(--sa-font);
+}
 </style>
 
 <style lang="scss">
-  .user-dropdown {
-    .status-up {
-      color: var(--el-color-success);
-    }
-    .status-down {
-      color: var(--el-color-danger);
-    }
+.user-dropdown {
+  .status-up {
+    color: var(--el-color-success);
+  }
+
+  .status-down {
+    color: var(--el-color-danger);
   }
+}
 </style>

+ 71 - 72
src/app/admin/views/auth/admin/password.vue

@@ -1,98 +1,97 @@
 <template>
   <el-container>
     <el-main>
-      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="100px">
-        <el-form-item label="用户名称">
+      <el-form :model="form.model" :rules="form.rules" ref="formRef" :label-width="formLabelWidth">
+        <el-form-item :label="t('modules.auth.username')">
           <el-input v-model="props.modal.params.name" disabled></el-input>
         </el-form-item>
-        <el-form-item label="新密码" prop="password">
-          <el-input
-            v-model="form.model.password"
-            type="password"
-            placeholder="请输入新密码"
-            show-password
-          ></el-input>
+        <el-form-item :label="t('modules.auth.newPassword')" prop="password">
+          <el-input v-model="form.model.password" type="password" :placeholder="t('modules.auth.passwordRequired')"
+            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.auth.confirmPassword')" prop="confirmPassword">
+          <el-input v-model="form.model.confirmPassword" type="password"
+            :placeholder="t('modules.auth.confirmPasswordRequired')" show-password></el-input>
         </el-form-item>
       </el-form>
     </el-main>
     <el-footer class="sa-footer--submit">
-      <el-button type="primary" @click="confirm">确定修改</el-button>
+      <el-button type="primary" @click="confirm">{{ t('modules.auth.changePassword') }}</el-button>
     </el-footer>
   </el-container>
 </template>
 
 <script setup>
-  import { reactive, ref, unref } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import admin from '@/app/admin/api';
+import { reactive, ref, unref } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import { useFormConfig } from '@/hooks/useFormConfig';
+import admin from '@/app/admin/api';
 
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
+const { t } = useI18n();
+
+// 使用表单配置hooks
+const { formLabelWidth } = useFormConfig({ enWidth: '150px' });
+
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps({
+  modal: {
+    type: Object,
+  },
+});
 
-  // 表单
-  const formRef = ref(null);
-  const form = reactive({
-    model: {
-      password: '',
-      confirmPassword: '',
-    },
-    rules: {
-      password: [
-        { required: true, message: '请输入新密码', trigger: 'blur' },
-        { min: 6, message: '密码长度不能少于6位', 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(null);
+const form = reactive({
+  model: {
+    password: '',
+    confirmPassword: '',
+  },
+  rules: {
+    password: [
+      { required: true, message: '请输入新密码', trigger: 'blur' },
+      { min: 6, message: '密码长度不能少于6位', trigger: 'blur' },
+    ],
+    confirmPassword: [
+      { required: true, message: '请再次输入新密码', trigger: 'blur' },
+      {
+        validator: (rule, value, callback) => {
+          if (value !== form.model.password) {
+            callback(new Error('两次输入的密码不一致'));
+          } else {
+            callback();
+          }
         },
-      ],
-    },
-  });
+        trigger: 'blur',
+      },
+    ],
+  },
+});
 
-  // 提交修改
-  function confirm() {
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
+// 提交修改
+function confirm() {
+  unref(formRef).validate(async (valid) => {
+    if (!valid) return;
 
-      try {
-        const { code } = await admin.auth.user.restpwd({
-          pwd: form.model.password,
-          userId: props.modal.params.id,
-        });
+    try {
+      const { code } = await admin.auth.user.restpwd({
+        pwd: form.model.password,
+        userId: props.modal.params.id,
+      });
 
-        if (code == 200) {
-          emit('modalCallBack', { event: 'confirm' });
-        }
-      } catch (error) {
-        console.error('修改密码失败:', error);
-        ElMessage.error('修改密码失败');
+      if (code == 200) {
+        emit('modalCallBack', { event: 'confirm' });
       }
-    });
-  }
+    } catch (error) {
+      console.error('修改密码失败:', error);
+      ElMessage.error(t('message.changePasswordFailed'));
+    }
+  });
+}
 </script>
 
 <style lang="scss" scoped>
-  .el-form {
-    max-width: 400px;
-  }
+.el-form {
+  max-width: 400px;
+}
 </style>

+ 131 - 125
src/app/admin/views/auth/role/edit.vue

@@ -1,155 +1,161 @@
 <template>
   <el-container>
     <el-main>
-      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="100px">
-        <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.auth.roleName')" prop="name">
+          <el-input v-model="form.model.name" :placeholder="t('modules.auth.roleRequired')"></el-input>
         </el-form-item>
-        <el-form-item label="权限信息">
-          <sa-access
-            type="select"
-            :isChangeParentId="isChangeParentId"
-            :role_id="props.modal.params.type === 'edit' ? props.modal.params.id : null"
-            :multiple="true"
-            v-model="form.model.permissionIds"
-          >
+        <el-form-item :label="t('modules.auth.rolePermissions')">
+          <sa-access type="select" :isChangeParentId="isChangeParentId"
+            :role_id="props.modal.params.type === 'edit' ? props.modal.params.id : null" :multiple="true"
+            v-model="form.model.permissionIds">
           </sa-access>
         </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 { reactive, ref, unref, onMounted } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import admin from '@/app/admin/api';
-  import SaAccess from '../access/components/sa-access.vue';
-  import { cloneDeep } from 'lodash';
-
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
+import { reactive, ref, unref, onMounted } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import { useFormConfig } from '@/hooks/useFormConfig';
+import admin from '@/app/admin/api';
+import SaAccess from '../access/components/sa-access.vue';
+import { cloneDeep } from 'lodash';
 
-  // 添加 编辑 form
-  const formRef = ref(null);
-  const form = reactive({
-    model: {
-      id: null,
-      name: '',
-      permissionIds: [], // 权限ID数组,用于sa-access组件
-    },
-    rules: {
-      name: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],
-    },
-  });
+const { t } = useI18n();
 
-  const isChangeParentId = ref(false);
+// 使用表单配置hooks
+const { formLabelWidth } = useFormConfig({ enWidth: '140px' });
 
-  // 获取详情
-  async function getDetail(id) {
-    try {
-      const { code, data } = await admin.auth.role.detail(id);
-      if (code == 200) {
-        // 设置基本信息
-        form.model.id = data.id;
-        form.model.name = data.name;
-        form.model.status = data.status || 'normal';
-
-        // 处理权限数据:从permissions数组中提取已选中的权限ID
-        if (data.permissions && Array.isArray(data.permissions)) {
-          const selectedIds = [];
-          const extractSelectedIds = (permissions) => {
-            permissions.forEach((permission) => {
-              if (permission.hasRelevance === '1') {
-                selectedIds.push(permission.id.toString());
-              }
-              if (permission.children && permission.children.length > 0) {
-                extractSelectedIds(permission.children);
-              }
-            });
-          };
-          extractSelectedIds(data.permissions);
-          form.model.permissionIds = selectedIds;
-        } else {
-          form.model.permissionIds = [];
-        }
-
-        console.log('角色详情数据:', form.model);
-        console.log('已选中的权限ID:', form.model.permissionIds);
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps({
+  modal: {
+    type: Object,
+  },
+});
+
+// 添加 编辑 form
+const formRef = ref(null);
+const form = reactive({
+  model: {
+    id: null,
+    name: '',
+    permissionIds: [], // 权限ID数组,用于sa-access组件
+  },
+  rules: {
+    name: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],
+  },
+});
+
+const isChangeParentId = ref(false);
+
+// 获取详情
+async function getDetail(id) {
+  try {
+    const { code, data } = await admin.auth.role.detail(id);
+    if (code == 200) {
+      // 设置基本信息
+      form.model.id = data.id;
+      form.model.name = data.name;
+      form.model.status = data.status || 'normal';
+
+      // 处理权限数据:从permissions数组中提取已选中的权限ID
+      if (data.permissions && Array.isArray(data.permissions)) {
+        const selectedIds = [];
+        const extractSelectedIds = (permissions) => {
+          permissions.forEach((permission) => {
+            if (permission.hasRelevance === '1') {
+              selectedIds.push(permission.id.toString());
+            }
+            if (permission.children && permission.children.length > 0) {
+              extractSelectedIds(permission.children);
+            }
+          });
+        };
+        extractSelectedIds(data.permissions);
+        form.model.permissionIds = selectedIds;
+      } else {
+        form.model.permissionIds = [];
       }
-    } catch (error) {
-      console.error('获取详情失败:', error);
-      ElMessage.error('获取详情失败');
+
+      console.log('角色详情数据:', form.model);
+      console.log('已选中的权限ID:', form.model.permissionIds);
     }
+  } catch (error) {
+    console.error('获取详情失败:', error);
+    ElMessage.error(t('message.fetchDetailFailed'));
   }
+}
+
+// 表单关闭时提交
+function confirm() {
+  // 表单验证
+  unref(formRef).validate(async (valid) => {
+    if (!valid) return;
 
-  // 表单关闭时提交
-  function confirm() {
-    // 表单验证
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-
-      try {
-        let submitForm = cloneDeep(form.model);
-        console.log('提交的表单数据:', submitForm);
-
-        // 处理权限数据:将权限ID数组转换为逗号分隔的字符串
-        if (submitForm.permissionIds && Array.isArray(submitForm.permissionIds)) {
-          submitForm.permissionIds = submitForm.permissionIds.join(',');
-        } else {
-          submitForm.permissionIds = '';
-        }
-
-        // 新增时不需要传递id字段
-        if (props.modal.params.type === 'add') {
-          delete submitForm.id;
-        }
-
-        console.log('最终提交数据:', submitForm);
-
-        const { code } =
-          props.modal.params.type == 'add'
-            ? await admin.auth.role.add(submitForm)
-            : await admin.auth.role.edit(submitForm);
-
-        if (code == 200) {
-          ElMessage.success(props.modal.params.type == 'add' ? '创建成功' : '更新成功');
-          emit('modalCallBack', { event: 'confirm' });
-        }
-      } catch (error) {
-        console.error('提交失败:', error);
-        ElMessage.error('提交失败');
+    try {
+      let submitForm = cloneDeep(form.model);
+      console.log('提交的表单数据:', submitForm);
+
+      // 处理权限数据:将权限ID数组转换为逗号分隔的字符串
+      if (submitForm.permissionIds && Array.isArray(submitForm.permissionIds)) {
+        submitForm.permissionIds = submitForm.permissionIds.join(',');
+      } else {
+        submitForm.permissionIds = '';
       }
-    });
-  }
 
-  onMounted(async () => {
-    if (props.modal.params.type == 'edit') {
-      await getDetail(props.modal.params.id);
+      // 新增时不需要传递id字段
+      if (props.modal.params.type === 'add') {
+        delete submitForm.id;
+      }
+
+      console.log('最终提交数据:', submitForm);
+
+      const { code } =
+        props.modal.params.type == 'add'
+          ? await admin.auth.role.add(submitForm)
+          : await admin.auth.role.edit(submitForm);
+
+      if (code == 200) {
+        ElMessage.success(props.modal.params.type == 'add' ? t('message.createSuccess') : t('message.updateSuccess'));
+        emit('modalCallBack', { event: 'confirm' });
+      }
+    } catch (error) {
+      console.error('提交失败:', error);
+      ElMessage.error(t('message.submitFailed'));
     }
   });
+}
+
+onMounted(async () => {
+  if (props.modal.params.type == 'edit') {
+    await getDetail(props.modal.params.id);
+  }
+});
 </script>
 <style lang="scss" scoped>
-  .sa-access {
-    height: 500px;
-    :deep() {
-      .el-main {
-        background: transparent;
-      }
-      .el-scrollbar {
-        border-top: 1px solid var(--sa-border);
-        border-bottom: 1px solid var(--sa-border);
-        &:first-of-type {
-          margin-left: 0;
-        }
+.sa-access {
+  height: 500px;
+
+  :deep() {
+    .el-main {
+      background: transparent;
+    }
+
+    .el-scrollbar {
+      border-top: 1px solid var(--sa-border);
+      border-bottom: 1px solid var(--sa-border);
+
+      &:first-of-type {
+        margin-left: 0;
       }
     }
   }
+}
 </style>

+ 94 - 101
src/app/admin/views/auth/role/index.vue

@@ -2,46 +2,35 @@
   <el-container class="role-view panel-block">
     <el-header class="sa-header">
       <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">角色列表</div>
+        <div class="label sa-flex">{{ t('modules.auth.roleList') }}</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 icon="Plus" type="primary" @click="addRow">{{ t('modules.auth.addRole') }}</el-button>
         </div>
       </div>
     </el-header>
     <el-main class="sa-p-0">
       <div class="sa-table-wrap panel-block panel-block--bottom" v-loading="loading">
-        <el-table
-          height="100%"
-          class="sa-table"
-          :data="table.data"
-          @sort-change="fieldFilter"
-          row-key="id"
-          stripe
-        >
+        <el-table height="100%" class="sa-table" :data="table.data" @sort-change="fieldFilter" row-key="id" stripe>
           <template #empty>
             <sa-empty />
           </template>
           <el-table-column prop="id" label="ID" min-width="100" sortable="custom">
           </el-table-column>
-          <el-table-column prop="name" label="角色名称" min-width="200" sortable="custom">
+          <el-table-column prop="name" :label="t('modules.auth.roleName')" min-width="200" sortable="custom">
             <template #default="scope">
               <span class="sa-table-line-1">{{ scope.row.name || '-' }}</span>
             </template>
           </el-table-column>
-          <el-table-column fixed="right" label="操作" min-width="120">
+          <el-table-column fixed="right" :label="t('common.actions')" min-width="120">
             <template #default="scope">
               <template v-if="scope.row.id != 1">
-                <el-button link type="primary" @click="editRow(scope.row)">编辑</el-button>
-                <el-popconfirm
-                  width="fit-content"
-                  confirm-button-text="确认"
-                  cancel-button-text="取消"
-                  title="确认删除这条记录?"
-                  @confirm="deleteApi(scope.row.id)"
-                >
+                <el-button link type="primary" @click="editRow(scope.row)">{{ t('common.edit') }}</el-button>
+                <el-popconfirm width="fit-content" :confirm-button-text="t('common.confirm')"
+                  :cancel-button-text="t('common.cancel')" :title="t('message.confirmDelete')"
+                  @confirm="deleteApi(scope.row.id)">
                   <template #reference>
-                    <el-button link type="danger">删除</el-button>
+                    <el-button link type="danger">{{ t('common.delete') }}</el-button>
                   </template>
                 </el-popconfirm>
               </template>
@@ -59,102 +48,106 @@
 </template>
 
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import admin from '@/app/admin/api';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import RoleEdit from './edit.vue';
-  const { pageData } = usePagination();
+import { onMounted, reactive, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+import admin from '@/app/admin/api';
+import { useModal, usePagination } from '@/sheep/hooks';
+import RoleEdit from './edit.vue';
 
-  // 列表
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-  });
-  const loading = ref(true);
+const { t } = useI18n();
+const { pageData } = usePagination();
 
-  // 获取数据
-  async function getData(page, searchParams = {}) {
-    if (page) pageData.page = page;
-    loading.value = true;
+// 列表
+const table = reactive({
+  data: [],
+  order: '',
+  sort: '',
+});
+const loading = ref(true);
 
-    try {
-      const { code, data } = await admin.auth.role.roleList({
-        page: 0,
-        size: pageData.size,
-        ...searchParams,
-      });
+// 获取数据
+async function getData(page, searchParams = {}) {
+  if (page) pageData.page = page;
+  loading.value = true;
 
-      if (code == 200) {
-        table.data = data.list;
-        pageData.page = data.pageNum;
-        pageData.size = data.pageSize;
-        pageData.total = data.total;
-      }
-    } catch (error) {
-      console.error('获取数据失败:', error);
-    } finally {
-      loading.value = false;
+  try {
+    const { code, data } = await admin.auth.role.roleList({
+      page: 0,
+      size: pageData.size,
+      ...searchParams,
+    });
+
+    if (code == 200) {
+      table.data = data.list;
+      pageData.page = data.pageNum;
+      pageData.size = data.pageSize;
+      pageData.total = data.total;
     }
+  } catch (error) {
+    console.error('获取数据失败:', error);
+  } finally {
+    loading.value = false;
   }
+}
 
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
+// table 字段排序
+function fieldFilter({ prop, order }) {
+  table.order = order == 'ascending' ? 'asc' : 'desc';
+  table.sort = prop;
+  getData();
+}
 
-  // 新建角色
-  function addRow() {
-    useModal(
-      RoleEdit,
-      { title: '新建角色', type: 'add' },
-      {
-        confirm: () => {
-          getData();
-        },
+// 新建角色
+function addRow() {
+  useModal(
+    RoleEdit,
+    { title: '新建角色', type: 'add' },
+    {
+      confirm: () => {
+        getData();
       },
-    );
-  }
+    },
+  );
+}
 
-  // 编辑角色
-  function editRow(row) {
-    useModal(
-      RoleEdit,
-      {
-        title: '编辑角色',
-        type: 'edit',
-        id: row.id,
+// 编辑角色
+function editRow(row) {
+  useModal(
+    RoleEdit,
+    {
+      title: '编辑角色',
+      type: 'edit',
+      id: row.id,
+    },
+    {
+      confirm: () => {
+        getData();
       },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
+    },
+  );
+}
 
-  // 删除api 单独批量可以直接调用
-  async function deleteApi(id) {
-    await admin.auth.role.delete({ id });
-    getData();
-  }
+// 删除api 单独批量可以直接调用
+async function deleteApi(id) {
+  await admin.auth.role.delete({ id });
+  getData();
+}
 
-  onMounted(() => {
-    getData();
-  });
+onMounted(() => {
+  getData();
+});
 </script>
 
 <style lang="scss" scoped>
-  .role-view {
-    .el-header {
-      height: auto;
-    }
-    .el-main {
-      .sa-table-wrap {
-        height: 100%;
-      }
+.role-view {
+  .el-header {
+    height: auto;
+  }
+
+  .el-main {
+    .sa-table-wrap {
+      height: 100%;
     }
   }
+}
 </style>

+ 104 - 113
src/app/admin/views/banner/edit.vue

@@ -1,146 +1,137 @@
 <template>
   <el-container>
-    <el-main v-loading="loading" element-loading-text="加载中...">
-      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="120px">
-        <el-form-item label="轮播图标题" prop="title">
-          <el-input
-            v-model="form.model.title"
-            placeholder="请输入轮播图标题"
-            maxlength="50"
-            show-word-limit
-          />
+    <el-main v-loading="loading" :element-loading-text="t('common.loading')">
+      <el-form :model="form.model" :rules="form.rules" ref="formRef" :label-width="formLabelWidth">
+        <el-form-item :label="t('modules.banner.bannerTitle')" prop="title">
+          <el-input v-model="form.model.title" :placeholder="t('modules.banner.enterBannerTitle')" maxlength="50"
+            show-word-limit />
         </el-form-item>
 
-        <el-form-item label="轮播图片" prop="image">
-          <sa-upload-image
-            v-model="imageArray"
-            :max-count="1"
-            :accept="['jpg', 'jpeg', 'png']"
-            :max-size="5"
-            :direct-upload="true"
-            :width="375"
-            :height="200"
-            placeholder="上传图片"
-          />
+        <el-form-item :label="t('modules.banner.bannerImage')" prop="image">
+          <sa-upload-image v-model="imageArray" :max-count="1" :accept="['jpg', 'jpeg', 'png']" :max-size="5"
+            :direct-upload="true" :width="375" :height="200" :placeholder="t('modules.banner.uploadImage')" />
         </el-form-item>
 
-        <el-form-item label="链接类型" prop="linkType">
+        <el-form-item :label="t('modules.banner.linkType')" prop="linkType">
           <el-radio-group v-model="form.model.linkType">
-            <el-radio :label="0">内链</el-radio>
-            <el-radio :label="1">外链</el-radio>
+            <el-radio :label="0">{{ t('modules.banner.internalLink') }}</el-radio>
+            <el-radio :label="1">{{ t('modules.banner.externalLink') }}</el-radio>
           </el-radio-group>
         </el-form-item>
 
-        <el-form-item label="链接地址" prop="link">
-          <el-input
-            v-model="form.model.link"
-            placeholder="请输入链接地址"
-            maxlength="200"
-            show-word-limit
-          />
+        <el-form-item :label="t('modules.banner.linkAddress')" prop="link">
+          <el-input v-model="form.model.link" :placeholder="t('modules.banner.enterLinkAddress')" maxlength="200"
+            show-word-limit />
         </el-form-item>
 
-        <el-form-item label="排序" prop="seq">
-          <el-input v-model="form.model.seq" placeholder="请填写排序" />
+        <el-form-item :label="t('common.sort')" prop="seq">
+          <el-input v-model="form.model.seq" :placeholder="t('common.enterSort')" />
         </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 { onMounted, reactive, ref, unref, computed } from 'vue';
-  import api from '../../api';
-  import { cloneDeep } from 'lodash';
-
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
-
-  // 添加 编辑 form
-  let formRef = ref(null);
-  const loading = ref(false);
-
-  // 图片数组和字符串的双向绑定
-  const imageArray = computed({
-    get() {
-      return form.model.image ? [form.model.image] : [];
-    },
-    set(value) {
-      form.model.image = value.length > 0 ? value[0] : '';
-    },
-  });
+import { onMounted, reactive, ref, unref, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useFormConfig } from '@/hooks/useFormConfig';
+import api from '../../api';
+import { cloneDeep } from 'lodash';
+
+const { t } = useI18n();
+
+// 使用表单配置hooks
+const { formLabelWidth } = useFormConfig({ enWidth: '140px' });
+
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps({
+  modal: {
+    type: Object,
+  },
+});
+
+// 添加 编辑 form
+let formRef = ref(null);
+const loading = ref(false);
+
+// 图片数组和字符串的双向绑定
+const imageArray = computed({
+  get() {
+    return form.model.image ? [form.model.image] : [];
+  },
+  set(value) {
+    form.model.image = value.length > 0 ? value[0] : '';
+  },
+});
+
+const form = reactive({
+  model: {
+    title: '',
+    image: '',
+    linkType: 0,
+    link: '',
+    seq: 0,
+  },
+  rules: {
+    title: [{ required: true, message: t('modules.banner.titleRequired'), trigger: 'blur' }],
+    image: [{ required: true, message: t('modules.banner.imageRequired'), trigger: 'change' }],
+    link: [{ required: true, message: t('modules.banner.linkRequired'), trigger: 'blur' }],
+  },
+});
+
+// 获取详情
+async function getDetail(id) {
+  loading.value = true;
+  const { code, data } = await api.banner.detail(id);
+  if (code == 200) {
+    form.model = data;
+  }
+  loading.value = false;
+}
+
+// 表单关闭时提交
+async function confirm() {
+  unref(formRef).validate(async (valid) => {
+    if (!valid) return;
+    let submitForm = cloneDeep(form.model);
+    if (props.modal.params.type == 'edit') {
+      submitForm.id = props.modal.params.id;
+    }
 
-  const form = reactive({
-    model: {
-      title: '',
-      image: '',
-      linkType: 0,
-      link: '',
-      seq: 0,
-    },
-    rules: {
-      title: [{ required: true, message: '请输入轮播图标题', trigger: 'blur' }],
-      image: [{ required: true, message: '请上传轮播图片', trigger: 'change' }],
-      link: [{ required: true, message: '请输入链接地址', trigger: 'blur' }],
-    },
-  });
+    const { code } =
+      props.modal.params.type == 'add'
+        ? await api.banner.add(submitForm)
+        : await api.banner.edit(submitForm);
 
-  // 获取详情
-  async function getDetail(id) {
-    loading.value = true;
-    const { code, data } = await api.banner.detail(id);
     if (code == 200) {
-      form.model = data;
+      emit('modalCallBack', { event: 'confirm' });
     }
-    loading.value = false;
-  }
-
-  // 表单关闭时提交
-  async function confirm() {
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-      let submitForm = cloneDeep(form.model);
-      if (props.modal.params.type == 'edit') {
-        submitForm.id = props.modal.params.id;
-      }
-
-      const { code } =
-        props.modal.params.type == 'add'
-          ? await api.banner.add(submitForm)
-          : await api.banner.edit(submitForm);
-
-      if (code == 200) {
-        emit('modalCallBack', { event: 'confirm' });
-      }
-    });
-  }
+  });
+}
 
-  async function init() {
-    if (props.modal.params.id) {
-      await getDetail(props.modal.params.id);
-    }
+async function init() {
+  if (props.modal.params.id) {
+    await getDetail(props.modal.params.id);
   }
+}
 
-  onMounted(() => {
-    init();
-  });
+onMounted(() => {
+  init();
+});
 </script>
 
 <style lang="scss" scoped>
-  .banner-edit {
-    .form-tip {
-      margin-top: 4px;
-      font-size: 12px;
-      color: #909399;
-      line-height: 1.4;
-    }
+.banner-edit {
+  .form-tip {
+    margin-top: 4px;
+    font-size: 12px;
+    color: #909399;
+    line-height: 1.4;
   }
+}
 </style>

+ 105 - 120
src/app/admin/views/banner/index.vue

@@ -2,86 +2,68 @@
   <el-container class="banner-view panel-block">
     <el-header class="sa-header">
       <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">轮播图管理</div>
+        <div class="label sa-flex">{{ t('modules.banner.title') }}</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 icon="Plus" type="primary" @click="addRow">{{ t('common.add') }}</el-button>
         </div>
       </div>
     </el-header>
     <el-main class="sa-p-0">
       <div class="sa-table-wrap panel-block panel-block--bottom" v-loading="loading">
-        <el-table
-          height="100%"
-          class="sa-table"
-          :data="table.data"
-          @sort-change="fieldFilter"
-          row-key="id"
-          stripe
-        >
+        <el-table height="100%" class="sa-table" :data="table.data" @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="100" sortable="custom">
           </el-table-column>
-          <el-table-column label="轮播图标题" min-width="200">
+          <el-table-column :label="t('modules.banner.bannerTitle')" min-width="200">
             <template #default="scope">
               <span class="sa-table-line-1">
                 {{ scope.row.title || '-' }}
               </span>
             </template>
           </el-table-column>
-          <el-table-column label="图片" min-width="120" align="center">
+          <el-table-column :label="t('modules.banner.image')" min-width="120" align="center">
             <template #default="scope">
-              <el-image
-                v-if="scope.row.image"
-                :src="scope.row.image"
-                style="width: 80px; height: 50px"
-                fit="cover"
-                :preview-src-list="[scope.row.image]"
-                :z-index="9999"
-                preview-teleported
-              />
+              <el-image v-if="scope.row.image" :src="scope.row.image" style="width: 80px; height: 50px" fit="cover"
+                :preview-src-list="[scope.row.image]" :z-index="9999" preview-teleported />
               <span v-else>-</span>
             </template>
           </el-table-column>
-          <el-table-column label="链接类型" min-width="100" align="center">
+          <el-table-column :label="t('modules.banner.linkType')" min-width="100" align="center">
             <template #default="scope">
               <el-tag :type="scope.row.linkType === 0 ? 'primary' : 'success'">
-                {{ scope.row.linkType === 0 ? '内链' : '外链' }}
+                {{ scope.row.linkType === 0 ? t('modules.banner.internalLink') : t('modules.banner.externalLink') }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="链接地址" min-width="200">
+          <el-table-column :label="t('modules.banner.linkAddress')" min-width="200">
             <template #default="scope">
               <span class="sa-table-line-1">
                 {{ scope.row.link || '-' }}
               </span>
             </template>
           </el-table-column>
-          <el-table-column label="排序" min-width="80" align="center">
+          <el-table-column :label="t('common.sort')" min-width="80" align="center">
             <template #default="scope">
               {{ scope.row.seq || 0 }}
             </template>
           </el-table-column>
-          <el-table-column label="创建时间" min-width="160">
+          <el-table-column :label="t('common.createTime')" min-width="160">
             <template #default="scope">
               {{ scope.row.createTime || '-' }}
             </template>
           </el-table-column>
-          <el-table-column fixed="right" label="操作">
+          <el-table-column fixed="right" :label="t('common.actions')">
             <template #default="scope">
-              <el-button class="is-link" type="primary" @click="editRow(scope.row)">编辑</el-button>
-              <el-popconfirm
-                width="fit-content"
-                confirm-button-text="确认"
-                cancel-button-text="取消"
-                title="确认删除这条记录?"
-                @confirm="deleteApi(scope.row.id)"
-              >
+              <el-button class="is-link" type="primary" @click="editRow(scope.row)">{{ t('common.edit') }}</el-button>
+              <el-popconfirm width="fit-content" :confirm-button-text="t('common.confirm')"
+                :cancel-button-text="t('common.cancel')" :title="t('message.confirmDelete')"
+                @confirm="deleteApi(scope.row.id)">
                 <template #reference>
-                  <el-button class="is-link" type="danger"> 删除 </el-button>
+                  <el-button class="is-link" type="danger">{{ t('common.delete') }}</el-button>
                 </template>
               </el-popconfirm>
             </template>
@@ -98,106 +80,109 @@
 </template>
 
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import api from '../../api';
-  import BannerEdit from './edit.vue';
+import { onMounted, reactive, ref } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import { useModal, usePagination } from '@/sheep/hooks';
+import api from '../../api';
+import BannerEdit from './edit.vue';
 
-  // 分页数据
-  const { pageData } = usePagination();
+const { t } = useI18n();
 
-  // 表格数据
-  const table = reactive({
-    data: [],
-    selection: [],
-  });
+// 分页数据
+const { pageData } = usePagination();
 
-  const loading = ref(false);
+// 表格数据
+const table = reactive({
+  data: [],
+  selection: [],
+});
 
-  // 获取数据
-  async function getData(page = pageData.page, searchData = {}) {
-    loading.value = true;
-    try {
-      const params = {
-        page: page,
-        size: pageData.size,
-        ...searchData,
-      };
+const loading = ref(false);
 
-      const { code, data } = await api.banner.list(params);
-      if (code == 200) {
-        table.data = data.list;
-        pageData.page = data.pageNum;
-        pageData.size = data.pageSize;
-        pageData.total = data.total;
-      }
-    } catch (error) {
-      console.error('获取轮播图列表失败:', error);
-      ElMessage.error('获取数据失败');
-    } finally {
-      loading.value = false;
+// 获取数据
+async function getData(page = pageData.page, searchData = {}) {
+  loading.value = true;
+  try {
+    const params = {
+      page: page,
+      size: pageData.size,
+      ...searchData,
+    };
+
+    const { code, data } = await api.banner.list(params);
+    if (code == 200) {
+      table.data = data.list;
+      pageData.page = data.pageNum;
+      pageData.size = data.pageSize;
+      pageData.total = data.total;
     }
+  } catch (error) {
+    console.error('获取轮播图列表失败:', error);
+    ElMessage.error(t('message.fetchDataFailed'));
+  } finally {
+    loading.value = false;
   }
+}
 
-  // 新建
-  function addRow() {
-    useModal(
-      BannerEdit,
-      {
-        title: '新建轮播图',
-        type: 'add',
-      },
-      {
-        confirm: () => {
-          getData();
-        },
+// 新建
+function addRow() {
+  useModal(
+    BannerEdit,
+    {
+      title: t('modules.banner.addBanner'),
+      type: 'add',
+    },
+    {
+      confirm: () => {
+        getData();
       },
-    );
-  }
+    },
+  );
+}
 
-  // 编辑
-  function editRow(row) {
-    useModal(
-      BannerEdit,
-      {
-        title: '编辑轮播图',
-        type: 'edit',
-        id: row.id,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
+// 编辑
+function editRow(row) {
+  useModal(
+    BannerEdit,
+    {
+      title: t('modules.banner.editBanner'),
+      type: 'edit',
+      id: row.id,
+    },
+    {
+      confirm: () => {
+        getData();
       },
-    );
-  }
+    },
+  );
+}
 
-  // 删除
-  async function deleteApi(id) {
-    try {
-      const { code } = await api.banner.delete({ id });
-      if (code == 200) {
-        getData();
-      }
-    } catch (error) {}
-  }
+// 删除
+async function deleteApi(id) {
+  try {
+    const { code } = await api.banner.delete({ id });
+    if (code == 200) {
+      getData();
+    }
+  } catch (error) { }
+}
 
-  // 字段排序
-  function fieldFilter({ prop, order }) {
-    // 实现排序逻辑
-    console.log('排序:', prop, order);
-  }
+// 字段排序
+function fieldFilter({ prop, order }) {
+  // 实现排序逻辑
+  console.log('排序:', prop, order);
+}
 
-  onMounted(() => {
-    getData();
-  });
+onMounted(() => {
+  getData();
+});
 </script>
 
 <style lang="scss" scoped>
-  .banner-view {
-    .search-container {
-      margin-bottom: 16px;
-    }
+.banner-view {
+  .search-container {
+    margin-bottom: 16px;
   }
+}
 </style>

+ 158 - 159
src/app/admin/views/index/profile/index.vue

@@ -3,23 +3,15 @@
     <el-main class="profile-main sa-p-0">
       <el-row :gutter="36">
         <el-col class="main-left" :xs="24" :sm="24" :md="24" :lg="8" :xl="10">
-          <div class="sa-title">用户信息</div>
+          <div class="sa-title">{{ t('modules.profile.userInfo') }}</div>
           <div class="top">
             <div class="content-name sa-m-b-16 sa-flex sa-row-between">
               <div class="sa-flex">
                 <div class="sa-m-r-20">
-                  <sa-image
-                    v-if="state.account.avatar"
-                    :url="state.account.avatar"
-                    size="64"
-                    radius="32"
-                  ></sa-image>
-                  <img
-                    v-else
-                    src="/static/images/shop/avatar.png"
+                  <sa-image v-if="state.account.avatar" :url="state.account.avatar" size="64" radius="32"></sa-image>
+                  <img v-else src="/static/images/shop/avatar.png"
                     style="width: 64px; height: 64px; border-radius: 32px; object-fit: cover"
-                    alt="默认头像"
-                  />
+                    :alt="t('modules.profile.defaultAvatar')" />
                 </div>
                 <div>
                   <div class="name sa-m-b-8">
@@ -31,36 +23,25 @@
                   </div>
                 </div>
               </div>
-              <el-button v-auth="'admin.index.update'" @click="selectImage" type="primary"
-                >上传头像</el-button
-              >
+              <el-button v-auth="'admin.index.update'" @click="selectImage" type="primary">{{
+                t('modules.profile.uploadAvatar') }}</el-button>
             </div>
             <div class="detail">
               <div class="detail-item">
-                <div class="label sa-m-b-8">用户名称</div>
+                <div class="label sa-m-b-8">{{ t('modules.profile.userName') }}</div>
                 <div class="sa-flex">
-                  <el-input
-                    readonly
-                    v-model="state.account.name"
-                    placeholder="请输入用户名称"
-                  ></el-input>
+                  <el-input readonly v-model="state.account.name"
+                    :placeholder="t('modules.profile.enterUserName')"></el-input>
                 </div>
               </div>
               <div class="detail-item">
-                <div class="label sa-m-b-8">登录账号</div>
-                <el-input
-                  readonly
-                  v-model="state.account.loginAccount"
-                  placeholder="请输入登录账号"
-                ></el-input>
+                <div class="label sa-m-b-8">{{ t('modules.profile.loginAccount') }}</div>
+                <el-input readonly v-model="state.account.loginAccount"
+                  :placeholder="t('modules.profile.enterLoginAccount')"></el-input>
               </div>
               <div class="detail-item">
-                <div class="label sa-m-b-8">角色</div>
-                <el-input
-                  readonly
-                  v-model="state.account.roleName"
-                  placeholder="roleName"
-                ></el-input>
+                <div class="label sa-m-b-8">{{ t('modules.profile.role') }}</div>
+                <el-input readonly v-model="state.account.roleName" placeholder="roleName"></el-input>
               </div>
               <!-- <div class="detail-item">
                 <div class="label sa-m-b-8">电子邮件</div>
@@ -77,8 +58,8 @@
             </div>
           </div>
           <div class="login">
-            <div class="sa-title">退出登录</div>
-            <el-button type="danger" @click="logout">退出登录</el-button>
+            <div class="sa-title">{{ t('modules.profile.logout') }}</div>
+            <el-button type="danger" @click="logout">{{ t('modules.profile.logout') }}</el-button>
           </div>
         </el-col>
       </el-row>
@@ -87,155 +68,173 @@
 </template>
 
 <script setup>
-  import { reactive, onMounted } from 'vue';
-  import { ElMessageBox, ElMessage } from 'element-plus';
-  import adminApi from '@/app/admin/api';
-  import { useFile } from '@/sheep/components/sa-file/sa-file-modal.vue';
-  import sheep from '@/sheep';
-
-  const accountStore = sheep.$store('account');
-
-  let state = reactive({
-    account: {},
-  });
-  async function getInfo() {
-    state.account = await accountStore.getInfo();
-  }
-  function updateAccount() {
-    adminApi.account.edit(state.account);
-  }
-  function logout() {
-    ElMessageBox.confirm('您确定要退出登录吗?', '退出登录', {
-      confirmButtonText: '确定',
-      cancelButtonText: '取消',
-      type: 'warning',
+import { reactive, onMounted } from 'vue';
+import { ElMessageBox, ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import adminApi from '@/app/admin/api';
+import { useFile } from '@/sheep/components/sa-file/sa-file-modal.vue';
+import sheep from '@/sheep';
+
+const { t } = useI18n();
+
+const accountStore = sheep.$store('account');
+
+let state = reactive({
+  account: {},
+});
+async function getInfo() {
+  state.account = await accountStore.getInfo();
+}
+function updateAccount() {
+  adminApi.account.edit(state.account);
+}
+function logout() {
+  ElMessageBox.confirm(t('modules.profile.confirmLogout'), t('modules.profile.logout'), {
+    confirmButtonText: t('common.confirm'),
+    cancelButtonText: t('common.cancel'),
+    type: 'warning',
+  })
+    .then(() => {
+      accountStore.logout();
     })
-      .then(() => {
-        accountStore.logout();
-      })
-      .catch(() => {
-        ElMessage({
-          type: 'info',
-          message: '已取消',
-        });
+    .catch(() => {
+      ElMessage({
+        type: 'info',
+        message: t('common.cancelled'),
       });
-  }
+    });
+}
 
-  function selectImage() {
-    useFile(
-      {
-        fileType: 'image',
-      },
-      {
-        confirm: (data) => {
-          state.account.avatar = data.url;
-        },
+function selectImage() {
+  useFile(
+    {
+      fileType: 'image',
+    },
+    {
+      confirm: (data) => {
+        state.account.avatar = data.url;
       },
-    );
-  }
+    },
+  );
+}
 
-  onMounted(() => {
-    getInfo();
-  });
+onMounted(() => {
+  getInfo();
+});
 </script>
 
 <style lang="scss" scoped>
-  .profile-view {
-    .profile-main {
-      overflow-x: hidden;
-    }
-    .main-left {
-      :deep() {
-        .el-input {
-          width: 100%;
-          max-width: 260px;
-        }
+.profile-view {
+  .profile-main {
+    overflow-x: hidden;
+  }
+
+  .main-left {
+    :deep() {
+      .el-input {
+        width: 100%;
+        max-width: 260px;
       }
     }
-    .top {
-      background: var(--sa-table-header-bg);
-      padding: 16px;
-      border-radius: 4px;
-      .content-name {
-        color: var(--sa-font);
-        font-size: 16px;
-        flex-wrap: wrap;
-        .name {
-          font-size: 20px;
-        }
-        .id {
-          color: var(--sa-subfont);
-          font-size: 16px;
-        }
+  }
+
+  .top {
+    background: var(--sa-table-header-bg);
+    padding: 16px;
+    border-radius: 4px;
+
+    .content-name {
+      color: var(--sa-font);
+      font-size: 16px;
+      flex-wrap: wrap;
+
+      .name {
+        font-size: 20px;
       }
-    }
-    .detail {
-      background: var(--sa-background-assist);
-      padding: 16px;
-      border-radius: 4px;
-      .detail-item {
+
+      .id {
+        color: var(--sa-subfont);
         font-size: 16px;
-        margin: 0 0 18px 0;
-        width: 100%;
-        &:last-child {
-          margin: 0;
-        }
-        .label {
-          font-size: 12px;
-          color: var(--sa-subfont);
-        }
       }
     }
-    .login,
-    .wechat {
-      color: var(--sa-subtitle);
-      img {
-        width: 32px;
-        height: 32px;
+  }
+
+  .detail {
+    background: var(--sa-background-assist);
+    padding: 16px;
+    border-radius: 4px;
+
+    .detail-item {
+      font-size: 16px;
+      margin: 0 0 18px 0;
+      width: 100%;
+
+      &:last-child {
+        margin: 0;
+      }
+
+      .label {
+        font-size: 12px;
+        color: var(--sa-subfont);
       }
     }
   }
+
+  .login,
+  .wechat {
+    color: var(--sa-subtitle);
+
+    img {
+      width: 32px;
+      height: 32px;
+    }
+  }
+}
 </style>
 
 <style lang="scss">
-  .el-dialog.sa-dialog.scan-dialog {
-    --el-dialog-width: 400px;
-    .profile-scan {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-    }
-    .scan-image {
-      position: relative;
+.el-dialog.sa-dialog.scan-dialog {
+  --el-dialog-width: 400px;
+
+  .profile-scan {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+
+  .scan-image {
+    position: relative;
+  }
+
+  .scan-image-tip {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    background: rgba(253, 253, 253, 0.94);
+    flex-direction: column;
+    justify-content: center;
+
+    .tip-code {
+      font-size: 18px;
+      color: var(--sa-title);
+      margin-bottom: 16px;
     }
-    .scan-image-tip {
-      position: absolute;
-      top: 0;
-      left: 0;
-      bottom: 0;
-      right: 0;
-      background: rgba(253, 253, 253, 0.94);
-      flex-direction: column;
-      justify-content: center;
-      .tip-code {
-        font-size: 18px;
-        color: var(--sa-title);
-        margin-bottom: 16px;
-      }
 
-      .tip-refresh {
-        font-size: 16px;
-        color: var(--el-color-primary);
-        cursor: pointer;
+    .tip-refresh {
+      font-size: 16px;
+      color: var(--el-color-primary);
+      cursor: pointer;
 
-        .el-icon {
-          font-size: 20px;
-        }
+      .el-icon {
+        font-size: 20px;
+      }
 
-        span {
-          line-height: 1;
-        }
+      span {
+        line-height: 1;
       }
     }
   }
+}
 </style>

+ 87 - 88
src/app/admin/views/payment/components/ChannelManage.vue

@@ -5,34 +5,30 @@
         <sa-empty />
       </template>
 
-      <el-table-column prop="channelCode" label="支付通道编号" min-width="120"> </el-table-column>
+      <el-table-column prop="channelCode" :label="t('modules.payment.channelCode')" min-width="120"> </el-table-column>
 
-      <el-table-column prop="channelName" label="通道名称" min-width="150"> </el-table-column>
+      <el-table-column prop="channelName" :label="t('modules.payment.channelName')" min-width="150"> </el-table-column>
 
-      <el-table-column prop="merchantNum" label="商户号" min-width="150"> </el-table-column>
+      <el-table-column prop="merchantNum" :label="t('modules.payment.merchantId')" min-width="150"> </el-table-column>
 
       <el-table-column prop="appid" label="APPID" min-width="200"> </el-table-column>
 
-      <el-table-column prop="secretKey" label="密钥" min-width="300">
+      <el-table-column prop="secretKey" :label="t('modules.payment.secretKey')" min-width="300">
         <template #default="scope">
           {{ scope.row.secretKey }}
         </template>
       </el-table-column>
 
-      <el-table-column label="操作" min-width="150" fixed="right">
+      <el-table-column :label="t('common.actions')" min-width="150" fixed="right">
         <template #default="scope">
           <el-button class="is-link" type="primary" @click="editChannel(scope.row)">
-            编辑
+            {{ t('common.edit') }}
           </el-button>
-          <el-popconfirm
-            width="fit-content"
-            confirm-button-text="确认"
-            cancel-button-text="取消"
-            title="确认删除这个支付通道?"
-            @confirm="deleteChannel(scope.row.id)"
-          >
+          <el-popconfirm width="fit-content" :confirm-button-text="t('common.confirm')"
+            :cancel-button-text="t('common.cancel')" :title="t('modules.payment.confirmDeleteChannel')"
+            @confirm="deleteChannel(scope.row.id)">
             <template #reference>
-              <el-button class="is-link" type="danger">删除</el-button>
+              <el-button class="is-link" type="danger">{{ t('common.delete') }}</el-button>
             </template>
           </el-popconfirm>
         </template>
@@ -42,90 +38,93 @@
 </template>
 
 <script setup>
-  import { onMounted, ref } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import { useModal } from '@/sheep/hooks';
-  import api from '../../../api';
-  import ChannelEdit from '../edit.vue';
-
-  const loading = ref(false);
-  const tableData = ref([]);
-
-  // 获取支付通道数据
-  async function getData() {
-    loading.value = true;
-    try {
-      const { code, data } = await api.payment.channel.list({});
-
-      if (code == 200) {
-        tableData.value = data || [];
-      }
-    } catch (error) {
-      console.error('获取支付通道失败:', error);
-      ElMessage.error('获取数据失败');
-    } finally {
-      loading.value = false;
+import { onMounted, ref } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import { useModal } from '@/sheep/hooks';
+import api from '../../../api';
+import ChannelEdit from '../edit.vue';
+
+const { t } = useI18n();
+
+const loading = ref(false);
+const tableData = ref([]);
+
+// 获取支付通道数据
+async function getData() {
+  loading.value = true;
+  try {
+    const { code, data } = await api.payment.channel.list({});
+
+    if (code == 200) {
+      tableData.value = data || [];
     }
+  } catch (error) {
+    console.error('获取支付通道失败:', error);
+    ElMessage.error(t('message.fetchDataFailed'));
+  } finally {
+    loading.value = false;
   }
-
-  // 编辑通道
-  function editChannel(channel) {
-    useModal(
-      ChannelEdit,
-      {
-        title: '编辑支付通道',
-        type: 'edit',
-        id: channel.id,
-        data: channel,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-
-  // 删除通道
-  async function deleteChannel(id) {
-    try {
-      const { code } = await api.payment.channel.delete(id);
-      if (code == 200) {
-        ElMessage.success('删除成功');
+}
+
+// 编辑通道
+function editChannel(channel) {
+  useModal(
+    ChannelEdit,
+    {
+      title: t('modules.payment.editPaymentChannel'),
+      type: 'edit',
+      id: channel.id,
+      data: channel,
+    },
+    {
+      confirm: () => {
         getData();
-      }
-    } catch (error) {
-      console.error('删除支付通道失败:', error);
-      ElMessage.error('删除失败');
+      },
+    },
+  );
+}
+
+// 删除通道
+async function deleteChannel(id) {
+  try {
+    const { code } = await api.payment.channel.delete(id);
+    if (code == 200) {
+      ElMessage.success(t('message.deleteSuccess'));
+      getData();
     }
+  } catch (error) {
+    console.error('删除支付通道失败:', error);
+    ElMessage.error(t('message.deleteFailed'));
   }
+}
 
-  // 暴露方法给父组件调用
-  defineExpose({
-    getData,
-  });
+// 暴露方法给父组件调用
+defineExpose({
+  getData,
+});
 
-  onMounted(() => {
-    getData();
-  });
+onMounted(() => {
+  getData();
+});
 </script>
 
 <style lang="scss" scoped>
-  .channel-manage {
-    height: 100%;
-    display: flex;
-    flex-direction: column;
-
-    .sa-table-wrap {
-      flex: 1;
-      margin-top: 16px;
-    }
+.channel-manage {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+
+  .sa-table-wrap {
+    flex: 1;
+    margin-top: 16px;
+  }
 
-    .secret-key {
-      font-family: 'Courier New', monospace;
-      font-size: 12px;
-      word-break: break-all;
-      line-height: 1.4;
-    }
+  .secret-key {
+    font-family: 'Courier New', monospace;
+    font-size: 12px;
+    word-break: break-all;
+    line-height: 1.4;
   }
+}
 </style>

+ 151 - 178
src/app/admin/views/payment/components/CollectionConfig.vue

@@ -5,79 +5,49 @@
         <sa-empty />
       </template>
 
-      <el-table-column label="代收支付方式" min-width="150" align="center">
+      <el-table-column :label="t('modules.payment.collectionMethod')" min-width="150" align="center">
         <template #default="scope">
           <div class="method-cell">
             <div class="method-name">{{ scope.row.methodName }}</div>
-            <el-switch
-              v-model="scope.row.status"
-              :active-value="1"
-              :inactive-value="0"
-              @change="handleMethodStatusChange(scope.row)"
-            />
+            <el-switch v-model="scope.row.status" :active-value="1" :inactive-value="0"
+              @change="handleMethodStatusChange(scope.row)" />
           </div>
         </template>
       </el-table-column>
 
-      <el-table-column label="代收支付通道" min-width="200" align="center">
+      <el-table-column :label="t('modules.payment.collectionChannel')" min-width="200" align="center">
         <template #default="scope">
           <div class="channel-list">
-            <div
-              v-for="channel in scope.row.midChannelMethodBOList"
-              :key="channel.id"
-              class="channel-item"
-            >
+            <div v-for="channel in scope.row.midChannelMethodBOList" :key="channel.id" class="channel-item">
               {{ channel.paymentChannel.channelName }}
             </div>
           </div>
         </template>
       </el-table-column>
 
-      <el-table-column label="权重" min-width="200" align="center">
+      <el-table-column :label="t('modules.payment.weight')" min-width="200" align="center">
         <template #default="scope">
           <div class="weight-list">
-            <div
-              v-for="channel in scope.row.midChannelMethodBOList"
-              :key="channel.id"
-              class="weight-item"
-            >
-              <el-input
-                v-model="channel.weight"
-                type="number"
-                :min="0"
-                size="small"
-                style="width: 80px"
-              />
+            <div v-for="channel in scope.row.midChannelMethodBOList" :key="channel.id" class="weight-item">
+              <el-input v-model="channel.weight" type="number" :min="0" size="small" style="width: 80px" />
               <span class="weight-percent ml-10px">
                 {{ calculateWeightPercent(scope.row, channel.weight) }}%
               </span>
-              <el-button
-                size="small"
-                type="primary"
-                @click="handleWeightSave(scope.row, channel)"
-                style="margin-left: 8px"
-              >
-                保存
+              <el-button size="small" type="primary" @click="handleWeightSave(scope.row, channel)"
+                style="margin-left: 8px">
+                {{ t('common.save') }}
               </el-button>
             </div>
           </div>
         </template>
       </el-table-column>
 
-      <el-table-column label="通道状态" min-width="150" align="center">
+      <el-table-column :label="t('modules.payment.channelStatus')" min-width="150" align="center">
         <template #default="scope">
           <div class="status-list">
-            <div
-              v-for="channel in scope.row.midChannelMethodBOList"
-              :key="channel.id"
-              class="status-item"
-            >
-              <el-switch
-                v-model="channel.status"
-                :active-value="1"
-                :inactive-value="0"
-                @change="handleChannelStatusChange(channel)"
-              />
+            <div v-for="channel in scope.row.midChannelMethodBOList" :key="channel.id" class="status-item">
+              <el-switch v-model="channel.status" :active-value="1" :inactive-value="0"
+                @change="handleChannelStatusChange(channel)" />
             </div>
           </div>
         </template>
@@ -87,161 +57,164 @@
 </template>
 
 <script setup>
-  import { onMounted, ref } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import api from '../../../api';
-
-  const loading = ref(false);
-  const tableData = ref([]);
-
-  // 获取代收配置数据
-  async function getData() {
-    loading.value = true;
-    try {
-      const { code, data } = await api.payment.method.list({
-        agencyService: 1, // 代收
-      });
-
-      if (code == 200) {
-        tableData.value = data || [];
-      }
-    } catch (error) {
-      console.error('获取代收配置失败:', error);
-      ElMessage.error('获取数据失败');
-    } finally {
-      loading.value = false;
+import { onMounted, ref } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import api from '../../../api';
+
+const { t } = useI18n();
+
+const loading = ref(false);
+const tableData = ref([]);
+
+// 获取代收配置数据
+async function getData() {
+  loading.value = true;
+  try {
+    const { code, data } = await api.payment.method.list({
+      agencyService: 1, // 代收
+    });
+
+    if (code == 200) {
+      tableData.value = data || [];
     }
+  } catch (error) {
+    console.error('获取代收配置失败:', error);
+    ElMessage.error(t('message.fetchDataFailed'));
+  } finally {
+    loading.value = false;
   }
-
-  // 计算权重百分比
-  function calculateWeightPercent(method, currentWeight) {
-    const totalWeight = method.midChannelMethodBOList.reduce((sum, channel) => {
-      return sum + (parseInt(channel.weight) || 0);
-    }, 0);
-
-    if (totalWeight === 0) return 0;
-    return (((parseInt(currentWeight) || 0) / totalWeight) * 100).toFixed(1);
-  }
-
-  // 处理支付方式状态变更
-  async function handleMethodStatusChange(method) {
-    try {
-      const { code } = await api.payment.method.update({
-        id: method.id,
-        status: method.status,
-      });
-
-      if (code == 200) {
-        ElMessage.success('状态更新成功');
-      }
-    } catch (error) {
-      console.error('更新支付方式状态失败:', error);
-      ElMessage.error('状态更新失败');
-      // 回滚状态
-      method.status = method.status === 1 ? 0 : 1;
+}
+
+// 计算权重百分比
+function calculateWeightPercent(method, currentWeight) {
+  const totalWeight = method.midChannelMethodBOList.reduce((sum, channel) => {
+    return sum + (parseInt(channel.weight) || 0);
+  }, 0);
+
+  if (totalWeight === 0) return 0;
+  return (((parseInt(currentWeight) || 0) / totalWeight) * 100).toFixed(1);
+}
+
+// 处理支付方式状态变更
+async function handleMethodStatusChange(method) {
+  try {
+    const { code } = await api.payment.method.update({
+      id: method.id,
+      status: method.status,
+    });
+
+    if (code == 200) {
+      ElMessage.success(t('modules.payment.statusUpdateSuccess'));
     }
+  } catch (error) {
+    console.error('更新支付方式状态失败:', error);
+    ElMessage.error(t('modules.payment.statusUpdateFailed'));
+    // 回滚状态
+    method.status = method.status === 1 ? 0 : 1;
   }
-
-  // 处理通道状态变更
-  async function handleChannelStatusChange(channel) {
-    try {
-      const { code } = await api.payment.method.midUpdate({
-        id: channel.id,
-        status: channel.status,
-      });
-
-      if (code == 200) {
-        ElMessage.success('通道状态更新成功');
-      }
-    } catch (error) {
-      console.error('更新通道状态失败:', error);
-      ElMessage.error('通道状态更新失败');
-      // 回滚状态
-      channel.status = channel.status === 1 ? 0 : 1;
+}
+
+// 处理通道状态变更
+async function handleChannelStatusChange(channel) {
+  try {
+    const { code } = await api.payment.method.midUpdate({
+      id: channel.id,
+      status: channel.status,
+    });
+
+    if (code == 200) {
+      ElMessage.success(t('modules.payment.channelStatusUpdateSuccess'));
     }
+  } catch (error) {
+    console.error('更新通道状态失败:', error);
+    ElMessage.error(t('modules.payment.channelStatusUpdateFailed'));
+    // 回滚状态
+    channel.status = channel.status === 1 ? 0 : 1;
   }
-
-  // 处理权重保存
-  async function handleWeightSave(method, channel) {
-    try {
-      const { code } = await api.payment.method.midUpdate({
-        id: channel.id,
-        weight: parseInt(channel.weight) || 0,
-      });
-
-      if (code == 200) {
-        ElMessage.success('权重保存成功');
-        // 重新获取数据以更新百分比显示
-        getData();
-      }
-    } catch (error) {
-      console.error('保存权重失败:', error);
-      ElMessage.error('权重保存失败');
-      // 重新获取数据以恢复原始值
+}
+
+// 处理权重保存
+async function handleWeightSave(method, channel) {
+  try {
+    const { code } = await api.payment.method.midUpdate({
+      id: channel.id,
+      weight: parseInt(channel.weight) || 0,
+    });
+
+    if (code == 200) {
+      ElMessage.success(t('modules.payment.weightSaveSuccess'));
+      // 重新获取数据以更新百分比显示
       getData();
     }
+  } catch (error) {
+    console.error('保存权重失败:', error);
+    ElMessage.error(t('modules.payment.weightSaveFailed'));
+    // 重新获取数据以恢复原始值
+    getData();
   }
+}
 
-  // 暴露方法给父组件调用
-  defineExpose({
-    getData,
-  });
+// 暴露方法给父组件调用
+defineExpose({
+  getData,
+});
 
-  onMounted(() => {
-    getData();
-  });
+onMounted(() => {
+  getData();
+});
 </script>
 
 <style lang="scss" scoped>
-  .collection-config {
-    height: 100%;
+.collection-config {
+  height: 100%;
 
-    .method-cell {
-      align-items: center;
-      gap: 12px;
+  .method-cell {
+    align-items: center;
+    gap: 12px;
 
-      .method-name {
-        font-weight: 500;
-      }
+    .method-name {
+      font-weight: 500;
     }
+  }
 
-    .channel-list,
-    .weight-list,
-    .status-list {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      gap: 0;
-      padding: 8px;
-    }
+  .channel-list,
+  .weight-list,
+  .status-list {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 0;
+    padding: 8px;
+  }
 
-    .channel-item,
-    .weight-item,
-    .status-item {
-      display: flex;
-      align-items: center;
-      min-height: 60px;
-      padding: 8px 12px;
-      width: 100%;
-      justify-content: center;
-
-      &:not(:last-child) {
-        border-bottom: 1px solid var(--el-border-color-light);
-        margin-bottom: 8px;
-        padding-bottom: 12px;
-      }
+  .channel-item,
+  .weight-item,
+  .status-item {
+    display: flex;
+    align-items: center;
+    min-height: 60px;
+    padding: 8px 12px;
+    width: 100%;
+    justify-content: center;
+
+    &:not(:last-child) {
+      border-bottom: 1px solid var(--el-border-color-light);
+      margin-bottom: 8px;
+      padding-bottom: 12px;
     }
+  }
 
-    .weight-item {
-      gap: 8px;
-      flex-wrap: wrap;
-      justify-content: center;
+  .weight-item {
+    gap: 8px;
+    flex-wrap: wrap;
+    justify-content: center;
 
-      .weight-percent {
-        color: var(--el-color-primary);
-        font-weight: 500;
-        min-width: 50px;
-      }
+    .weight-percent {
+      color: var(--el-color-primary);
+      font-weight: 500;
+      min-width: 50px;
     }
   }
+}
 </style>

+ 94 - 103
src/app/admin/views/payment/components/PayoutConfig.vue

@@ -5,40 +5,28 @@
         <sa-empty />
       </template>
 
-      <el-table-column label="代付支付方式" min-width="150" align="center">
+      <el-table-column :label="t('modules.payment.payoutMethod')" min-width="150" align="center">
         <template #default="scope">
           <span class="method-name">{{ scope.row.methodName }}</span>
         </template>
       </el-table-column>
 
-      <el-table-column label="代付支付通道" min-width="200" align="center">
+      <el-table-column :label="t('modules.payment.payoutChannel')" min-width="200" align="center">
         <template #default="scope">
           <div class="channel-list">
-            <div
-              v-for="channel in scope.row.midChannelMethodBOList"
-              :key="channel.id"
-              class="channel-item"
-            >
+            <div v-for="channel in scope.row.midChannelMethodBOList" :key="channel.id" class="channel-item">
               {{ channel.paymentChannel.channelName }}
             </div>
           </div>
         </template>
       </el-table-column>
 
-      <el-table-column label="通道状态" min-width="150" align="center">
+      <el-table-column :label="t('modules.payment.channelStatus')" min-width="150" align="center">
         <template #default="scope">
           <div class="status-list">
-            <div
-              v-for="channel in scope.row.midChannelMethodBOList"
-              :key="channel.id"
-              class="status-item"
-            >
-              <el-switch
-                v-model="channel.status"
-                :active-value="1"
-                :inactive-value="0"
-                @change="handleChannelStatusChange(channel)"
-              />
+            <div v-for="channel in scope.row.midChannelMethodBOList" :key="channel.id" class="status-item">
+              <el-switch v-model="channel.status" :active-value="1" :inactive-value="0"
+                @change="handleChannelStatusChange(channel)" />
             </div>
           </div>
         </template>
@@ -48,105 +36,108 @@
 </template>
 
 <script setup>
-  import { onMounted, ref } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import api from '../../../api';
-
-  const loading = ref(false);
-  const tableData = ref([]);
-
-  // 获取代付配置数据
-  async function getData() {
-    loading.value = true;
-    try {
-      const { code, data } = await api.payment.method.list({
-        agencyService: 2, // 代付
-      });
-
-      if (code == 200) {
-        tableData.value = data || [];
-      }
-    } catch (error) {
-      console.error('获取代付配置失败:', error);
-      ElMessage.error('获取数据失败');
-    } finally {
-      loading.value = false;
+import { onMounted, ref } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import api from '../../../api';
+
+const { t } = useI18n();
+
+const loading = ref(false);
+const tableData = ref([]);
+
+// 获取代付配置数据
+async function getData() {
+  loading.value = true;
+  try {
+    const { code, data } = await api.payment.method.list({
+      agencyService: 2, // 代付
+    });
+
+    if (code == 200) {
+      tableData.value = data || [];
     }
+  } catch (error) {
+    console.error('获取代付配置失败:', error);
+    ElMessage.error(t('message.fetchDataFailed'));
+  } finally {
+    loading.value = false;
   }
-
-  // 处理通道状态变更
-  async function handleChannelStatusChange(channel) {
-    try {
-      const { code } = await api.payment.method.midUpdate({
-        id: channel.id,
-        status: channel.status,
-      });
-
-      if (code == 200) {
-        ElMessage.success('通道状态更新成功');
-      }
-    } catch (error) {
-      console.error('更新通道状态失败:', error);
-      ElMessage.error('通道状态更新失败');
-      // 回滚状态
-      channel.status = channel.status === 1 ? 0 : 1;
+}
+
+// 处理通道状态变更
+async function handleChannelStatusChange(channel) {
+  try {
+    const { code } = await api.payment.method.midUpdate({
+      id: channel.id,
+      status: channel.status,
+    });
+
+    if (code == 200) {
+      ElMessage.success(t('modules.payment.channelStatusUpdateSuccess'));
     }
+  } catch (error) {
+    console.error('更新通道状态失败:', error);
+    ElMessage.error(t('modules.payment.channelStatusUpdateFailed'));
+    // 回滚状态
+    channel.status = channel.status === 1 ? 0 : 1;
   }
+}
 
-  // 暴露方法给父组件调用
-  defineExpose({
-    getData,
-  });
+// 暴露方法给父组件调用
+defineExpose({
+  getData,
+});
 
-  onMounted(() => {
-    getData();
-  });
+onMounted(() => {
+  getData();
+});
 </script>
 
 <style lang="scss" scoped>
-  .payout-config {
-    height: 100%;
+.payout-config {
+  height: 100%;
 
-    .method-cell {
-      display: flex;
-      align-items: center;
+  .method-cell {
+    display: flex;
+    align-items: center;
 
-      .method-name {
-        font-weight: 500;
-      }
+    .method-name {
+      font-weight: 500;
     }
+  }
 
-    .channel-list,
-    .weight-list,
-    .status-list {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      justify-content: center;
-      gap: 0;
-      padding: 8px;
-    }
+  .channel-list,
+  .weight-list,
+  .status-list {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    gap: 0;
+    padding: 8px;
+  }
 
-    .channel-item,
-    .weight-item,
-    .status-item {
-      display: flex;
-      align-items: center;
-      min-height: 40px;
-      padding: 8px 12px;
-      width: 100%;
-      justify-content: center;
-
-      &:not(:last-child) {
-        border-bottom: 1px solid var(--el-border-color-light);
-        margin-bottom: 8px;
-        padding-bottom: 12px;
-      }
+  .channel-item,
+  .weight-item,
+  .status-item {
+    display: flex;
+    align-items: center;
+    min-height: 40px;
+    padding: 8px 12px;
+    width: 100%;
+    justify-content: center;
+
+    &:not(:last-child) {
+      border-bottom: 1px solid var(--el-border-color-light);
+      margin-bottom: 8px;
+      padding-bottom: 12px;
     }
+  }
 
-    .weight-display {
-      color: #909399;
-      font-style: italic;
-    }
+  .weight-display {
+    color: #909399;
+    font-style: italic;
   }
+}
 </style>

+ 100 - 116
src/app/admin/views/payment/edit.vue

@@ -1,148 +1,132 @@
 <template>
   <el-container>
-    <el-main v-loading="loading" element-loading-text="加载中...">
-      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="120px">
-        <el-form-item label="通道编号" prop="channelCode">
-          <el-input
-            v-model="form.model.channelCode"
-            placeholder="请输入支付通道编号"
-            maxlength="50"
-            show-word-limit
-          />
+    <el-main v-loading="loading" :element-loading-text="t('common.loading')">
+      <el-form :model="form.model" :rules="form.rules" ref="formRef" :label-width="formLabelWidth">
+        <el-form-item :label="t('modules.payment.channelCode')" prop="channelCode">
+          <el-input v-model="form.model.channelCode" :placeholder="t('modules.payment.enterChannelCode')" maxlength="50"
+            show-word-limit />
         </el-form-item>
 
-        <el-form-item label="通道名称" prop="channelName">
-          <el-input
-            v-model="form.model.channelName"
-            placeholder="请输入通道名称"
-            maxlength="100"
-            show-word-limit
-          />
+        <el-form-item :label="t('modules.payment.channelName')" prop="channelName">
+          <el-input v-model="form.model.channelName" :placeholder="t('modules.payment.enterChannelName')"
+            maxlength="100" show-word-limit />
         </el-form-item>
 
-        <el-form-item label="商户号" prop="merchantNum">
-          <el-input
-            v-model="form.model.merchantNum"
-            placeholder="请输入商户号"
-            maxlength="100"
-            show-word-limit
-          />
+        <el-form-item :label="t('modules.payment.merchantId')" prop="merchantNum">
+          <el-input v-model="form.model.merchantNum" :placeholder="t('modules.payment.enterMerchantId')" maxlength="100"
+            show-word-limit />
         </el-form-item>
 
         <el-form-item label="APPID" prop="appid">
-          <el-input
-            v-model="form.model.appid"
-            placeholder="请输入应用ID"
-            maxlength="200"
-            show-word-limit
-          />
+          <el-input v-model="form.model.appid" :placeholder="t('modules.payment.enterAppId')" maxlength="200"
+            show-word-limit />
         </el-form-item>
 
-        <el-form-item label="密钥" prop="secretKey">
-          <el-input
-            v-model="form.model.secretKey"
-            type="textarea"
-            :rows="4"
-            placeholder="请输入密钥"
-            maxlength="1000"
-            show-word-limit
-          />
+        <el-form-item :label="t('modules.payment.secretKey')" prop="secretKey">
+          <el-input v-model="form.model.secretKey" type="textarea" :rows="4"
+            :placeholder="t('modules.payment.enterSecretKey')" maxlength="1000" show-word-limit />
         </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 { onMounted, reactive, ref, unref } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import api from '../../api';
-
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
-
-  // 添加 编辑 form
-  let formRef = ref(null);
-  const loading = ref(false);
-
-  const form = reactive({
-    model: {
-      channelCode: '',
-      channelName: '',
-      merchantNum: '',
-      appid: '',
-      secretKey: '',
-    },
-    rules: {
-      channelCode: [{ required: true, message: '请输入支付通道编号', trigger: 'blur' }],
-      channelName: [{ required: true, message: '请输入通道名称', trigger: 'blur' }],
-      merchantNum: [{ required: true, message: '请输入商户号', trigger: 'blur' }],
-      appid: [{ required: true, message: '请输入应用ID', trigger: 'blur' }],
-      secretKey: [{ required: true, message: '请输入密钥', trigger: 'blur' }],
-    },
-  });
-
-  // 获取详情
-  async function getDetail(data) {
-    loading.value = true;
-    try {
-      form.model.channelCode = data.channelCode || '';
-      form.model.channelName = data.channelName || '';
-      form.model.merchantNum = data.merchantNum || '';
-      form.model.appid = data.appid || '';
-      form.model.secretKey = data.secretKey || '';
-    } finally {
-      loading.value = false;
-    }
+import { onMounted, reactive, ref, unref } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import { useFormConfig } from '@/hooks/useFormConfig';
+import api from '../../api';
+
+const { t } = useI18n();
+
+// 使用表单配置hooks
+const { formLabelWidth } = useFormConfig({ enWidth: '140px' });
+
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps({
+  modal: {
+    type: Object,
+  },
+});
+
+// 添加 编辑 form
+let formRef = ref(null);
+const loading = ref(false);
+
+const form = reactive({
+  model: {
+    channelCode: '',
+    channelName: '',
+    merchantNum: '',
+    appid: '',
+    secretKey: '',
+  },
+  rules: {
+    channelCode: [{ required: true, message: '请输入支付通道编号', trigger: 'blur' }],
+    channelName: [{ required: true, message: '请输入通道名称', trigger: 'blur' }],
+    merchantNum: [{ required: true, message: '请输入商户号', trigger: 'blur' }],
+    appid: [{ required: true, message: '请输入应用ID', trigger: 'blur' }],
+    secretKey: [{ required: true, message: '请输入密钥', trigger: 'blur' }],
+  },
+});
+
+// 获取详情
+async function getDetail(data) {
+  loading.value = true;
+  try {
+    form.model.channelCode = data.channelCode || '';
+    form.model.channelName = data.channelName || '';
+    form.model.merchantNum = data.merchantNum || '';
+    form.model.appid = data.appid || '';
+    form.model.secretKey = data.secretKey || '';
+  } finally {
+    loading.value = false;
   }
+}
 
-  // 表单关闭时提交
-  async function confirm() {
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-
-      let submitForm = { ...form.model };
+// 表单关闭时提交
+async function confirm() {
+  unref(formRef).validate(async (valid) => {
+    if (!valid) return;
 
-      if (props.modal.params.type == 'edit') {
-        submitForm.id = props.modal.params.id;
-      }
+    let submitForm = { ...form.model };
 
-      const { code } =
-        props.modal.params.type == 'add'
-          ? await api.payment.channel.add(submitForm)
-          : await api.payment.channel.update(submitForm);
+    if (props.modal.params.type == 'edit') {
+      submitForm.id = props.modal.params.id;
+    }
 
-      if (code == 200) {
-        ElMessage.success('保存成功');
-        emit('modalCallBack', { event: 'confirm' });
-      }
-    });
-  }
+    const { code } =
+      props.modal.params.type == 'add'
+        ? await api.payment.channel.add(submitForm)
+        : await api.payment.channel.update(submitForm);
 
-  async function init() {
-    if (props.modal.params.type === 'edit' && props.modal.params.data) {
-      await getDetail(props.modal.params.data);
+    if (code == 200) {
+      ElMessage.success('保存成功');
+      emit('modalCallBack', { event: 'confirm' });
     }
+  });
+}
+
+async function init() {
+  if (props.modal.params.type === 'edit' && props.modal.params.data) {
+    await getDetail(props.modal.params.data);
   }
+}
 
-  onMounted(() => {
-    init();
-  });
+onMounted(() => {
+  init();
+});
 </script>
 
 <style lang="scss" scoped>
-  // .el-textarea {
-  //   :deep(.el-textarea__inner) {
-  //     font-family: 'Courier New', monospace;
-  //     font-size: 14px;
-  //   }
-  // }
-</style>
+// .el-textarea {
+//   :deep(.el-textarea__inner) {
+//     font-family: 'Courier New', monospace;
+//     font-size: 14px;
+//   }
+// }</style>

+ 64 - 65
src/app/admin/views/payment/index.vue

@@ -2,22 +2,18 @@
   <el-container class="payment-config">
     <el-header class="sa-header">
       <el-tabs class="sa-tabs" v-model="activeTab" @tab-change="handleTabChange">
-        <el-tab-pane label="代收配置" name="collection"></el-tab-pane>
-        <el-tab-pane label="代付配置" name="payout"></el-tab-pane>
-        <el-tab-pane label="支付通道" name="channel"></el-tab-pane>
+        <el-tab-pane :label="t('modules.payment.collectionConfig')" name="collection"></el-tab-pane>
+        <el-tab-pane :label="t('modules.payment.payoutConfig')" name="payout"></el-tab-pane>
+        <el-tab-pane :label="t('modules.payment.paymentChannel')" name="channel"></el-tab-pane>
       </el-tabs>
       <div class="sa-title sa-flex sa-row-between">
         <div class="label sa-flex">
           <span class="left"></span>
         </div>
         <div>
-          <el-button
-            class="sa-button-refresh"
-            icon="RefreshRight"
-            @click="refreshCurrentTab"
-          ></el-button>
+          <el-button class="sa-button-refresh" icon="RefreshRight" @click="refreshCurrentTab"></el-button>
           <el-button v-if="activeTab === 'channel'" icon="Plus" type="primary" @click="addChannel">
-            新增通道
+            {{ t('modules.payment.addChannel') }}
           </el-button>
         </div>
       </div>
@@ -33,75 +29,78 @@
 </template>
 
 <script setup>
-  import { ref } from 'vue';
-  import { useModal } from '@/sheep/hooks';
-  import CollectionConfig from './components/CollectionConfig.vue';
-  import PayoutConfig from './components/PayoutConfig.vue';
-  import ChannelManage from './components/ChannelManage.vue';
-  import ChannelEdit from './edit.vue';
+import { ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useModal } from '@/sheep/hooks';
+import CollectionConfig from './components/CollectionConfig.vue';
+import PayoutConfig from './components/PayoutConfig.vue';
+import ChannelManage from './components/ChannelManage.vue';
+import ChannelEdit from './edit.vue';
 
-  // 当前激活的Tab
-  const activeTab = ref('collection');
+const { t } = useI18n();
 
-  // 组件引用
-  const collectionRef = ref();
-  const payoutRef = ref();
-  const channelRef = ref();
+// 当前激活的Tab
+const activeTab = ref('collection');
 
-  // Tab切换处理
-  function handleTabChange(tabName) {
-    activeTab.value = tabName;
-    // 切换Tab时重新调用对应组件的接口
-    refreshCurrentTab();
-  }
+// 组件引用
+const collectionRef = ref();
+const payoutRef = ref();
+const channelRef = ref();
 
-  // 刷新当前Tab的数据
-  function refreshCurrentTab() {
-    switch (activeTab.value) {
-      case 'collection':
-        collectionRef.value?.getData?.();
-        break;
-      case 'payout':
-        payoutRef.value?.getData?.();
-        break;
-      case 'channel':
-        channelRef.value?.getData?.();
-        break;
-    }
+// Tab切换处理
+function handleTabChange(tabName) {
+  activeTab.value = tabName;
+  // 切换Tab时重新调用对应组件的接口
+  refreshCurrentTab();
+}
+
+// 刷新当前Tab的数据
+function refreshCurrentTab() {
+  switch (activeTab.value) {
+    case 'collection':
+      collectionRef.value?.getData?.();
+      break;
+    case 'payout':
+      payoutRef.value?.getData?.();
+      break;
+    case 'channel':
+      channelRef.value?.getData?.();
+      break;
   }
+}
 
-  // 新增支付通道
-  function addChannel() {
-    useModal(
-      ChannelEdit,
-      {
-        title: '新增支付通道',
-        type: 'add',
-      },
-      {
-        confirm: () => {
-          channelRef.value?.getData?.();
-        },
+// 新增支付通道
+function addChannel() {
+  useModal(
+    ChannelEdit,
+    {
+      title: t('modules.payment.addPaymentChannel'),
+      type: 'add',
+    },
+    {
+      confirm: () => {
+        channelRef.value?.getData?.();
       },
-    );
-  }
+    },
+  );
+}
 </script>
 
 <style lang="scss" scoped>
-  .payment-config {
-    height: 100%;
+.payment-config {
+  height: 100%;
 
-    .payment-tabs {
-      height: 100%;
+  .payment-tabs {
+    height: 100%;
 
-      :deep(.el-tabs__content) {
-        height: calc(100% - 40px);
-        padding: 0;
-      }
+    :deep(.el-tabs__content) {
+      height: calc(100% - 40px);
+      padding: 0;
+    }
 
-      :deep(.el-tab-pane) {
-        height: 100%;
-      }
+    :deep(.el-tab-pane) {
+      height: 100%;
     }
   }
+}
 </style>

+ 157 - 158
src/app/shop/admin/content/banner/index.vue

@@ -3,12 +3,8 @@
     <el-header class="sa-header">
       <!-- 简化搜索组件 -->
       <div class="search-container">
-        <sa-search-simple
-          :searchFields="searchFields"
-          :defaultValues="defaultSearchValues"
-          @search="(val) => getData(1, val)"
-          @reset="getData(1)"
-        >
+        <sa-search-simple :searchFields="searchFields" :defaultValues="defaultSearchValues"
+          v-model="currentSearchParams" @search="handleSearch" @reset="handleReset">
         </sa-search-simple>
       </div>
       <div class="sa-title sa-flex sa-row-between">
@@ -21,15 +17,8 @@
     </el-header>
     <el-main class="sa-p-0">
       <div class="sa-table-wrap panel-block panel-block--bottom" 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>
@@ -45,12 +34,7 @@
           </el-table-column>
           <el-table-column label="广告图片" min-width="120">
             <template #default="scope">
-              <el-image
-                v-if="scope.row.image"
-                :src="scope.row.image"
-                style="width: 60px; height: 40px"
-                fit="cover"
-              />
+              <el-image v-if="scope.row.image" :src="scope.row.image" style="width: 60px; height: 40px" fit="cover" />
               <span v-else>-</span>
             </template>
           </el-table-column>
@@ -88,13 +72,8 @@
           <el-table-column fixed="right" label="操作" min-width="120">
             <template #default="scope">
               <el-button class="is-link" type="primary" @click="editRow(scope.row)">编辑</el-button>
-              <el-popconfirm
-                width="fit-content"
-                confirm-button-text="确认"
-                cancel-button-text="取消"
-                title="确认删除这条记录?"
-                @confirm="deleteApi(scope.row.id)"
-              >
+              <el-popconfirm width="fit-content" confirm-button-text="确认" cancel-button-text="取消" title="确认删除这条记录?"
+                @confirm="deleteApi(scope.row.id)">
                 <template #reference>
                   <el-button class="is-link" type="danger"> 删除 </el-button>
                 </template>
@@ -106,11 +85,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" />
@@ -119,141 +95,164 @@
   </el-container>
 </template>
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../content.service';
-  import { ElMessageBox } from 'element-plus';
-  import { useModal } from '@/sheep/hooks';
-  import { usePagination } from '@/sheep/hooks';
-  import bannerEdit from './edit.vue';
-  const { pageData } = usePagination();
+import { onMounted, reactive, ref } from 'vue';
+import { api } from '../content.service';
+import { ElMessageBox } from 'element-plus';
+import { useModal } from '@/sheep/hooks';
+import { usePagination } from '@/sheep/hooks';
+import bannerEdit from './edit.vue';
+const { pageData } = usePagination();
 
-  // 搜索字段配置
-  const searchFields = reactive({
-    title: {
-      type: 'input',
-      label: '广告标题',
-      placeholder: '请输入广告标题',
-      width: 200,
-    },
-  });
-  // 默认搜索值
-  const defaultSearchValues = reactive({
-    title: '',
-  });
-  // 列表
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
+// 搜索字段配置
+const searchFields = reactive({
+  title: {
+    type: 'input',
+    label: '广告标题',
+    placeholder: '请输入广告标题',
+    width: 200,
+  },
+});
+// 默认搜索值
+const defaultSearchValues = reactive({
+  title: '',
+});
+
+// 当前搜索条件 - 使用 ref 支持双向绑定
+const currentSearchParams = ref({});
+
+// 列表
+const table = reactive({
+  data: [],
+  order: '',
+  sort: '',
+  selected: [],
+});
+const loading = ref(true);
+// 获取数据
+async function getData(page, searchParams = null) {
+  if (page) pageData.page = page;
+  loading.value = true;
+
+  // 构建请求参数 - 优先使用传入的参数,否则使用双向绑定的搜索条件
+  const finalSearchParams = searchParams !== null ? searchParams : currentSearchParams.value;
+
+  const { code, data } = await api.list({
+    page: pageData.page,
+    size: pageData.size,
+    order: table.order,
+    ...finalSearchParams,
+    sort: table.sort,
   });
-  const loading = ref(true);
-  // 获取
-  async function getData(page, searchParams = {}) {
-    if (page) pageData.page = page;
-    loading.value = true;
-    const { code, data } = await api.list({
-      page: pageData.page,
-      size: pageData.size,
-      order: table.order,
-      ...searchParams,
-      sort: table.sort,
-    });
-    console.log('API 响应:', error, data);
-    if (code == 200) {
-      table.data = data.data;
-      pageData.page = data.current_page;
-      pageData.size = data.per_page;
-      pageData.total = data.total;
-    }
-    loading.value = false;
+  console.log('API 响应:', error, data);
+  if (code == 200) {
+    table.data = data.data;
+    pageData.page = data.current_page;
+    pageData.size = data.per_page;
+    pageData.total = data.total;
   }
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
-  //table批量选择
-  function changeSelection(row) {
-    table.selected = row;
-  }
-  // 分页/批量操作
-  const batchHandleTools = [
+  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 = [
+  {
+    type: 'delete',
+    label: '删除',
+    auth: 'shop.admin.content.banner.delete',
+    class: 'danger',
+  },
+];
+function addRow() {
+  useModal(
+    bannerEdit,
+    { title: '新建广告', type: 'add' },
     {
-      type: 'delete',
-      label: '删除',
-      auth: 'shop.admin.content.banner.delete',
-      class: 'danger',
-    },
-  ];
-  function addRow() {
-    useModal(
-      bannerEdit,
-      { title: '新建广告', type: 'add' },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-  function editRow(row) {
-    useModal(
-      bannerEdit,
-      {
-        title: '编辑广告',
-        type: 'edit',
-        id: row.id,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
+      confirm: () => {
+        getData();
       },
-    );
-  }
-  // 删除api 单独批量可以直接调用
-  async function deleteApi(id) {
-    await api.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;
-      default:
-        await api.edit(ids.join(','), {
-          status: type,
-        });
+    },
+  );
+}
+function editRow(row) {
+  useModal(
+    bannerEdit,
+    {
+      title: '编辑广告',
+      type: 'edit',
+      id: row.id,
+    },
+    {
+      confirm: () => {
         getData();
-    }
+      },
+    },
+  );
+}
+// 删除api 单独批量可以直接调用
+async function deleteApi(id) {
+  await api.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;
+    default:
+      await api.edit(ids.join(','), {
+        status: type,
+      });
+      getData();
   }
+}
 
-  onMounted(() => {
-    getData();
-  });
+// 搜索处理
+const handleSearch = (searchParams) => {
+  // 由于使用了 v-model,currentSearchParams 会自动更新
+  // 直接调用 getData,会自动使用当前的搜索条件
+  getData(1);
+};
+
+// 重置处理
+const handleReset = () => {
+  // 由于使用了 v-model,currentSearchParams 会自动清空
+  // 直接调用 getData,会自动使用当前的搜索条件
+  getData(1);
+};
+
+onMounted(() => {
+  getData();
+});
 </script>
 <style lang="scss" scoped>
-  .banner-view {
-    .el-header {
-      height: auto;
-    }
-    .el-main {
-      .sa-table-wrap {
-        height: 100%;
-      }
+.banner-view {
+  .el-header {
+    height: auto;
+  }
+
+  .el-main {
+    .sa-table-wrap {
+      height: 100%;
     }
   }
+}
 </style>

+ 156 - 152
src/app/shop/admin/content/notification/index.vue

@@ -3,12 +3,8 @@
     <el-header class="sa-header">
       <!-- 简化搜索组件 -->
       <div class="search-container">
-        <sa-search-simple
-          :searchFields="searchFields"
-          :defaultValues="defaultSearchValues"
-          @search="(val) => getData(1, val)"
-          @reset="getData(1)"
-        >
+        <sa-search-simple :searchFields="searchFields" :defaultValues="defaultSearchValues"
+          v-model="currentSearchParams" @search="handleSearch" @reset="handleReset">
         </sa-search-simple>
       </div>
       <div class="sa-title sa-flex sa-row-between">
@@ -21,15 +17,8 @@
     </el-header>
     <el-main class="sa-p-0">
       <div class="sa-table-wrap panel-block panel-block--bottom" 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>
@@ -75,13 +64,8 @@
           <el-table-column fixed="right" label="操作" min-width="120">
             <template #default="scope">
               <el-button class="is-link" type="primary" @click="editRow(scope.row)">编辑</el-button>
-              <el-popconfirm
-                width="fit-content"
-                confirm-button-text="确认"
-                cancel-button-text="取消"
-                title="确认删除这条记录?"
-                @confirm="deleteApi(scope.row.id)"
-              >
+              <el-popconfirm width="fit-content" confirm-button-text="确认" cancel-button-text="取消" title="确认删除这条记录?"
+                @confirm="deleteApi(scope.row.id)">
                 <template #reference>
                   <el-button class="is-link" type="danger"> 删除 </el-button>
                 </template>
@@ -93,11 +77,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" />
@@ -106,141 +87,164 @@
   </el-container>
 </template>
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../content.service';
-  import { ElMessageBox } from 'element-plus';
-  import { useModal } from '@/sheep/hooks';
-  import { usePagination } from '@/sheep/hooks';
-  import notificationEdit from './edit.vue';
-  const { pageData } = usePagination();
+import { onMounted, reactive, ref } from 'vue';
+import { api } from '../content.service';
+import { ElMessageBox } from 'element-plus';
+import { useModal } from '@/sheep/hooks';
+import { usePagination } from '@/sheep/hooks';
+import notificationEdit from './edit.vue';
+const { pageData } = usePagination();
 
-  // 搜索字段配置
-  const searchFields = reactive({
-    title: {
-      type: 'input',
-      label: '消息标题',
-      placeholder: '请输入消息标题',
-      width: 200,
-    },
-  });
-  // 默认搜索值
-  const defaultSearchValues = reactive({
-    title: '',
-  });
-  // 列表
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
+// 搜索字段配置
+const searchFields = reactive({
+  title: {
+    type: 'input',
+    label: '消息标题',
+    placeholder: '请输入消息标题',
+    width: 200,
+  },
+});
+// 默认搜索值
+const defaultSearchValues = reactive({
+  title: '',
+});
+
+// 当前搜索条件 - 使用 ref 支持双向绑定
+const currentSearchParams = ref({});
+
+// 列表
+const table = reactive({
+  data: [],
+  order: '',
+  sort: '',
+  selected: [],
+});
+const loading = ref(true);
+// 获取数据
+async function getData(page, searchParams = null) {
+  if (page) pageData.page = page;
+  loading.value = true;
+
+  // 构建请求参数 - 优先使用传入的参数,否则使用双向绑定的搜索条件
+  const finalSearchParams = searchParams !== null ? searchParams : currentSearchParams.value;
+
+  const { code, data } = await api.list({
+    page: pageData.page,
+    size: pageData.size,
+    order: table.order,
+    ...finalSearchParams,
+    sort: table.sort,
   });
-  const loading = ref(true);
-  // 获取
-  async function getData(page, searchParams = {}) {
-    if (page) pageData.page = page;
-    loading.value = true;
-    const { code, data } = await api.list({
-      page: pageData.page,
-      size: pageData.size,
-      order: table.order,
-      ...searchParams,
-      sort: table.sort,
-    });
-    console.log('API 响应:', error, data);
-    if (code == 200) {
-      table.data = data.data;
-      pageData.page = data.current_page;
-      pageData.size = data.per_page;
-      pageData.total = data.total;
-    }
-    loading.value = false;
+  console.log('API 响应:', error, data);
+  if (code == 200) {
+    table.data = data.data;
+    pageData.page = data.current_page;
+    pageData.size = data.per_page;
+    pageData.total = data.total;
   }
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
-  //table批量选择
-  function changeSelection(row) {
-    table.selected = row;
-  }
-  // 分页/批量操作
-  const batchHandleTools = [
+  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 = [
+  {
+    type: 'delete',
+    label: '删除',
+    auth: 'shop.admin.content.notification.delete',
+    class: 'danger',
+  },
+];
+function addRow() {
+  useModal(
+    notificationEdit,
+    { title: '新建消息', type: 'add' },
     {
-      type: 'delete',
-      label: '删除',
-      auth: 'shop.admin.content.notification.delete',
-      class: 'danger',
-    },
-  ];
-  function addRow() {
-    useModal(
-      notificationEdit,
-      { title: '新建消息', type: 'add' },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-  function editRow(row) {
-    useModal(
-      notificationEdit,
-      {
-        title: '编辑消息',
-        type: 'edit',
-        id: row.id,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
+      confirm: () => {
+        getData();
       },
-    );
-  }
-  // 删除api 单独批量可以直接调用
-  async function deleteApi(id) {
-    await api.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;
-      default:
-        await api.edit(ids.join(','), {
-          status: type,
-        });
+    },
+  );
+}
+function editRow(row) {
+  useModal(
+    notificationEdit,
+    {
+      title: '编辑消息',
+      type: 'edit',
+      id: row.id,
+    },
+    {
+      confirm: () => {
         getData();
-    }
+      },
+    },
+  );
+}
+// 删除api 单独批量可以直接调用
+async function deleteApi(id) {
+  await api.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;
+    default:
+      await api.edit(ids.join(','), {
+        status: type,
+      });
+      getData();
   }
+}
 
-  onMounted(() => {
-    getData();
-  });
+// 搜索处理
+const handleSearch = (searchParams) => {
+  // 由于使用了 v-model,currentSearchParams 会自动更新
+  // 直接调用 getData,会自动使用当前的搜索条件
+  getData(1);
+};
+
+// 重置处理
+const handleReset = () => {
+  // 由于使用了 v-model,currentSearchParams 会自动清空
+  // 直接调用 getData,会自动使用当前的搜索条件
+  getData(1);
+};
+
+onMounted(() => {
+  getData();
+});
 </script>
 <style lang="scss" scoped>
-  .notification-view {
-    .el-header {
-      height: auto;
-    }
-    .el-main {
-      .sa-table-wrap {
-        height: 100%;
-      }
+.notification-view {
+  .el-header {
+    height: auto;
+  }
+
+  .el-main {
+    .sa-table-wrap {
+      height: 100%;
     }
   }
+}
 </style>

+ 150 - 138
src/app/shop/admin/data/report/index.vue

@@ -3,12 +3,8 @@
     <el-header class="sa-header">
       <!-- 简化搜索组件 -->
       <div class="search-container">
-        <sa-search-simple
-          :searchFields="searchFields"
-          :defaultValues="defaultSearchValues"
-          @search="(val) => getData(1, val)"
-          @reset="getData(1)"
-        >
+        <sa-search-simple :searchFields="searchFields" :defaultValues="defaultSearchValues"
+          v-model="currentSearchParams" @search="handleSearch" @reset="handleReset">
         </sa-search-simple>
       </div>
       <div class="sa-title sa-flex sa-row-between">
@@ -60,15 +56,8 @@
 
       <!-- 数据表格 -->
       <div class="sa-table-wrap panel-block panel-block--bottom" 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>
@@ -104,11 +93,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" />
@@ -117,143 +103,169 @@
   </el-container>
 </template>
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../data.service';
-  import { ElMessage } from 'element-plus';
-  import { usePagination } from '@/sheep/hooks';
-  const { pageData } = usePagination();
+import { onMounted, reactive, ref } from 'vue';
+import { api } from '../data.service';
+import { ElMessage } from 'element-plus';
+import { usePagination } from '@/sheep/hooks';
+const { pageData } = usePagination();
 
-  // 搜索字段配置
-  const searchFields = reactive({
-    date_range: {
-      type: 'daterange',
-      label: '日期范围',
-      placeholder: '请选择日期范围',
-      width: 300,
-    },
-  });
-  // 默认搜索值
-  const defaultSearchValues = reactive({
-    date_range: [],
-  });
+// 搜索字段配置
+const searchFields = reactive({
+  date_range: {
+    type: 'daterange',
+    label: '日期范围',
+    placeholder: '请选择日期范围',
+    width: 300,
+  },
+});
+// 默认搜索值
+const defaultSearchValues = reactive({
+  date_range: [],
+});
 
-  // 统计数据
-  const statsData = reactive({
-    totalSales: 0,
-    totalOrders: 0,
-    totalUsers: 0,
-    totalGoods: 0,
-  });
+// 当前搜索条件 - 使用 ref 支持双向绑定
+const currentSearchParams = ref({});
 
-  // 列表
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-  const loading = ref(true);
+// 统计数据
+const statsData = reactive({
+  totalSales: 0,
+  totalOrders: 0,
+  totalUsers: 0,
+  totalGoods: 0,
+});
 
-  // 获取统计数据
-  async function getStatsData() {
-    const { code, data } = await api.report.getStats();
-    if (code == 200) {
-      Object.assign(statsData, data);
-    }
-  }
+// 列表
+const table = reactive({
+  data: [],
+  order: '',
+  sort: '',
+  selected: [],
+});
+const loading = ref(true);
 
-  // 获取
-  async function getData(page, searchParams = {}) {
-    if (page) pageData.page = page;
-    loading.value = true;
-    const { code, data } = await api.report.list({
-      page: pageData.page,
-      size: pageData.size,
-      order: table.order,
-      ...searchParams,
-      sort: table.sort,
-    });
-    console.log('API 响应:', error, data);
-    if (code == 200) {
-      table.data = data.data;
-      pageData.page = data.current_page;
-      pageData.size = data.per_page;
-      pageData.total = data.total;
-    }
-    loading.value = false;
+// 获取统计数据
+async function getStatsData() {
+  const { code, data } = await api.report.getStats();
+  if (code == 200) {
+    Object.assign(statsData, data);
   }
+}
 
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
+// 获取数据
+async function getData(page, searchParams = null) {
+  if (page) pageData.page = page;
+  loading.value = true;
 
-  //table批量选择
-  function changeSelection(row) {
-    table.selected = row;
-  }
+  // 构建请求参数 - 优先使用传入的参数,否则使用双向绑定的搜索条件
+  const finalSearchParams = searchParams !== null ? searchParams : currentSearchParams.value;
 
-  // 导出数据
-  async function exportData() {
-    ElMessage.success('导出功能开发中...');
+  const { code, data } = await api.report.list({
+    page: pageData.page,
+    size: pageData.size,
+    order: table.order,
+    ...finalSearchParams,
+    sort: table.sort,
+  });
+  console.log('API 响应:', error, data);
+  if (code == 200) {
+    table.data = data.data;
+    pageData.page = data.current_page;
+    pageData.size = data.per_page;
+    pageData.total = data.total;
   }
+  loading.value = false;
+}
 
-  // 分页/批量操作
-  const batchHandleTools = [
-    {
-      type: 'export',
-      label: '导出选中',
-      auth: 'shop.admin.data.report.export',
-      class: 'primary',
-    },
-  ];
+// table 字段排序
+function fieldFilter({ prop, order }) {
+  table.order = order == 'ascending' ? 'asc' : 'desc';
+  table.sort = prop;
+  getData();
+}
 
-  async function batchHandle(type) {
-    let ids = [];
-    table.selected.forEach((row) => {
-      ids.push(row.id);
-    });
-    switch (type) {
-      case 'export':
-        ElMessage.success('批量导出功能开发中...');
-        break;
-    }
-  }
+//table批量选择
+function changeSelection(row) {
+  table.selected = row;
+}
+
+// 导出数据
+async function exportData() {
+  ElMessage.success('导出功能开发中...');
+}
+
+// 分页/批量操作
+const batchHandleTools = [
+  {
+    type: 'export',
+    label: '导出选中',
+    auth: 'shop.admin.data.report.export',
+    class: 'primary',
+  },
+];
 
-  onMounted(() => {
-    getStatsData();
-    getData();
+async function batchHandle(type) {
+  let ids = [];
+  table.selected.forEach((row) => {
+    ids.push(row.id);
   });
+  switch (type) {
+    case 'export':
+      ElMessage.success('批量导出功能开发中...');
+      break;
+  }
+}
+
+// 搜索处理
+const handleSearch = (searchParams) => {
+  // 由于使用了 v-model,currentSearchParams 会自动更新
+  // 直接调用 getData,会自动使用当前的搜索条件
+  getData(1);
+};
+
+// 重置处理
+const handleReset = () => {
+  // 由于使用了 v-model,currentSearchParams 会自动清空
+  // 直接调用 getData,会自动使用当前的搜索条件
+  getData(1);
+};
+
+onMounted(() => {
+  getStatsData();
+  getData();
+});
 </script>
 <style lang="scss" scoped>
-  .data-report-view {
-    .el-header {
-      height: auto;
-    }
-    .el-main {
-      .stats-cards {
-        margin-bottom: 20px;
-        .stats-card {
-          .stats-content {
-            text-align: center;
-            .stats-value {
-              font-size: 24px;
-              font-weight: bold;
-              color: #409eff;
-              margin-bottom: 8px;
-            }
-            .stats-label {
-              font-size: 14px;
-              color: #666;
-            }
+.data-report-view {
+  .el-header {
+    height: auto;
+  }
+
+  .el-main {
+    .stats-cards {
+      margin-bottom: 20px;
+
+      .stats-card {
+        .stats-content {
+          text-align: center;
+
+          .stats-value {
+            font-size: 24px;
+            font-weight: bold;
+            color: #409eff;
+            margin-bottom: 8px;
+          }
+
+          .stats-label {
+            font-size: 14px;
+            color: #666;
           }
         }
       }
-      .sa-table-wrap {
-        height: calc(100% - 140px);
-      }
+    }
+
+    .sa-table-wrap {
+      height: calc(100% - 140px);
     }
   }
+}
 </style>

+ 208 - 214
src/app/shop/admin/finance/commission/detail.vue

@@ -3,17 +3,17 @@
     <el-main>
       <!-- 基本信息 -->
       <div class="basic-info sa-m-b-26">
-        <h3 class="sa-m-b-20">基本信息</h3>
+        <h3 class="sa-m-b-20">{{ t('modules.commission.basicInfo') }}</h3>
         <el-row :gutter="20">
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">交易号:</span>
+              <span class="label">{{ t('modules.commission.transactionNo') }}:</span>
               <span class="value">{{ commissionDetail.tranNo || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">状态:</span>
+              <span class="label">{{ t('common.status') }}:</span>
               <el-tag :type="getStatusType('commission', commissionDetail.status)">
                 {{ getStatusText('commission', commissionDetail.status) }}
               </el-tag>
@@ -21,13 +21,13 @@
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">佣金类型:</span>
+              <span class="label">{{ t('modules.commission.commissionType') }}:</span>
               <span class="value">{{ getBizTypeText(commissionDetail.bizType) }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">用户名:</span>
+              <span class="label">{{ t('modules.commission.userName') }}:</span>
               <span class="value">{{ commissionDetail.name || '--' }}</span>
             </div>
           </el-col>
@@ -35,25 +35,25 @@
         <el-row :gutter="20" class="sa-m-t-12">
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">手机号:</span>
+              <span class="label">{{ t('modules.commission.phoneNo') }}:</span>
               <span class="value">{{ commissionDetail.phoneNo || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">交易金额:</span>
+              <span class="label">{{ t('modules.commission.transactionAmount') }}:</span>
               <span class="value">৳{{ commissionDetail.amount || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">余额:</span>
+              <span class="label">{{ t('modules.commission.balance') }}:</span>
               <span class="value">৳{{ commissionDetail.balance || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">订单号:</span>
+              <span class="label">{{ t('modules.commission.orderNo') }}:</span>
               <span class="value">{{ commissionDetail.orderNo || '--' }}</span>
             </div>
           </el-col>
@@ -61,25 +61,25 @@
         <el-row :gutter="20" class="sa-m-t-12">
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">创建时间:</span>
+              <span class="label">{{ t('modules.commission.createTime') }}:</span>
               <span class="value">{{ commissionDetail.createTime || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">交易时间:</span>
+              <span class="label">{{ t('modules.commission.issueTime') }}:</span>
               <span class="value">{{ commissionDetail.transTime || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">结算时间:</span>
+              <span class="label">{{ t('modules.commission.settleTime') }}:</span>
               <span class="value">{{ commissionDetail.settleTime || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
             <div class="info-item">
-              <span class="label">备注:</span>
+              <span class="label">{{ t('modules.commission.remark') }}:</span>
               <span class="value">{{ commissionDetail.memo || '--' }}</span>
             </div>
           </el-col>
@@ -88,34 +88,28 @@
 
       <!-- 操作日志 -->
       <div class="operation-logs sa-m-b-26 mt-40px">
-        <h3 class="sa-m-b-20">操作日志</h3>
+        <h3 class="sa-m-b-20">{{ t('modules.commission.operationLog') }}</h3>
         <div class="sa-table-wrap">
-          <el-table
-            class="sa-table"
-            :data="operationLogs.data"
-            v-loading="operationLogs.loading"
-            stripe
-            border
-          >
+          <el-table class="sa-table" :data="operationLogs.data" v-loading="operationLogs.loading" stripe border>
             <template #empty>
               <sa-empty />
             </template>
-            <el-table-column prop="time" label="时间" min-width="160" align="center">
+            <el-table-column prop="time" :label="t('modules.commission.operationTime')" min-width="160" align="center">
               <template #default="scope">
                 {{ scope.row.time || '--' }}
               </template>
             </el-table-column>
-            <el-table-column prop="operator" label="操作人" min-width="120" align="center">
+            <el-table-column prop="operator" :label="t('modules.commission.operator')" min-width="120" align="center">
               <template #default="scope">
                 {{ scope.row.operator || '--' }}
               </template>
             </el-table-column>
-            <el-table-column prop="action" label="事项" min-width="200">
+            <el-table-column prop="action" :label="t('modules.commission.operationType')" min-width="200">
               <template #default="scope">
                 {{ scope.row.action || '--' }}
               </template>
             </el-table-column>
-            <el-table-column prop="remark" label="备注" min-width="300">
+            <el-table-column prop="remark" :label="t('modules.commission.remark')" min-width="300">
               <template #default="scope">
                 {{ scope.row.remark || '--' }}
               </template>
@@ -125,11 +119,8 @@
 
         <!-- 分页 -->
         <div class="sa-m-t-16" v-if="operationLogs.total > 0">
-          <sa-pagination
-            :pageData="operationLogs.pageData"
-            @updateFn="getOperationLogs"
-            layout="total, prev, pager, next"
-          />
+          <sa-pagination :pageData="operationLogs.pageData" @updateFn="getOperationLogs"
+            layout="total, prev, pager, next" />
         </div>
       </div>
     </el-main>
@@ -137,188 +128,214 @@
 </template>
 
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../finance.service';
-  import { usePagination } from '@/sheep/hooks';
-
-  const props = defineProps(['modal']);
-  const emit = defineEmits(['modalCallBack']);
-
-  const commissionDetail = ref({});
-  const loading = ref(false);
-
-  // 操作日志分页
-  const { pageData: logPageData } = usePagination();
-  const operationLogs = reactive({
-    data: [],
-    loading: false,
-    total: 0,
-    pageData: logPageData,
-  });
-
-  // 通用状态处理函数
-  const getStatusText = (type, value) => {
-    // 对于佣金状态,直接使用financeUtils中的函数
-    if (type === 'commission') {
-      const statusMap = {
-        1: '未结算',
-        2: '已结算',
-      };
-      return statusMap[value] || '未知';
-    }
-    return '未知';
+import { onMounted, reactive, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { api } from '../finance.service';
+import { usePagination } from '@/sheep/hooks';
+
+const { t } = useI18n();
+
+const props = defineProps(['modal']);
+const emit = defineEmits(['modalCallBack']);
+
+const commissionDetail = ref({});
+const loading = ref(false);
+
+// 操作日志分页
+const { pageData: logPageData } = usePagination();
+const operationLogs = reactive({
+  data: [],
+  loading: false,
+  total: 0,
+  pageData: logPageData,
+});
+
+// 通用状态处理函数
+const getStatusText = (type, value) => {
+  // 对于佣金状态,使用国际化文本
+  if (type === 'commission') {
+    const statusMap = {
+      1: t('modules.commission.unsettled'),
+      2: t('modules.commission.settled'),
+    };
+    return statusMap[value] || t('modules.commission.unknown');
+  }
+  return t('modules.commission.unknown');
+};
+
+const getStatusType = (type, value) => {
+  // 对于佣金状态,直接使用financeUtils中的函数
+  if (type === 'commission') {
+    const statusMap = {
+      1: 'warning', // 未结算
+      2: 'success', // 已结算
+    };
+    return statusMap[value] || 'info';
+  }
+  return 'info';
+};
+
+// 获取佣金类型文本
+function getBizTypeText(bizType) {
+  const typeMap = {
+    1001: t('modules.commission.recharge'),
+    2001: t('modules.commission.withdraw'),
+    3001: t('modules.commission.groupPayment'),
+    3002: t('modules.commission.joinPayment'),
+    4001: t('modules.commission.orderRefund'),
+    4002: t('modules.commission.commissionRefund'),
+    5001: t('modules.commission.inviteReward'),
+    5002: t('modules.commission.levelReward'),
+    5003: t('modules.commission.taskReward'),
+    5004: t('modules.commission.inviteReward'),
+    5005: t('modules.commission.levelReward'),
+    6001: t('modules.commission.recharge'),
+    7001: t('modules.commission.withdraw'),
   };
+  return typeMap[bizType] || t('modules.commission.unknown');
+}
+
+// 获取佣金详情
+async function getCommissionDetail() {
+  if (!props.modal?.params?.id) return;
+
+  loading.value = true;
+  try {
+    const { code, data } = await api.commission.detail(props.modal.params.id);
+    if (code == '200') {
+      commissionDetail.value = data;
+    }
+  } catch (error) {
+    console.error('获取佣金详情失败:', error);
+  } finally {
+    loading.value = false;
+  }
+}
+
+// 获取操作日志
+function getOperationLogs(page) {
+  if (page) operationLogs.pageData.page = page;
+  operationLogs.loading = true;
+
+  try {
+    // 模拟操作日志数据
+    const mockLogs = [
+      {
+        time: '2025/06/06 12:30:30',
+        operator: 'Aamir Khan',
+        action: '创建佣金记录',
+        remark: '--',
+      },
+      {
+        time: '2025/06/06 12:36:30',
+        operator: 'system',
+        action: '佣金状态更新为处理中',
+        remark: '--',
+      },
+      {
+        time: '2025/06/06 12:40:30',
+        operator: 'admin',
+        action: '佣金结算完成',
+        remark: '正常结算',
+      },
+    ];
+
+    operationLogs.data = mockLogs;
+    operationLogs.total = mockLogs.length;
+  } catch (error) {
+    console.error('获取操作日志失败:', error);
+  } finally {
+    operationLogs.loading = false;
+  }
+}
 
-  const getStatusType = (type, value) => {
-    // 对于佣金状态,直接使用financeUtils中的函数
-    if (type === 'commission') {
-      const statusMap = {
-        1: 'warning', // 未结算
-        2: 'success', // 已结算
-      };
-      return statusMap[value] || 'info';
+onMounted(() => {
+  getCommissionDetail();
+  getOperationLogs();
+});
+</script>
+
+<style lang="scss" scoped>
+.commission-detail {
+  .basic-info {
+    h3 {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--sa-title);
     }
-    return 'info';
-  };
 
-  // 获取佣金类型文本
-  function getBizTypeText(bizType) {
-    const typeMap = {
-      1001: '充值',
-      2001: '提现',
-      3001: '开团支付',
-      3002: '参团支付',
-      4001: '未成团退款',
-      4002: '成团退款',
-      5001: '开团红包收益',
-      5002: '参团红包收益',
-      5003: '签到红包收益',
-      5004: '下级红包佣金',
-      5005: '下下级红包佣金',
-      6001: '充值返点',
-      7001: '提现手续费',
-    };
-    return typeMap[bizType] || '未知类型';
-  }
+    .info-item {
+      display: flex;
+      align-items: center;
+      margin-bottom: 8px;
 
-  // 获取佣金详情
-  async function getCommissionDetail() {
-    if (!props.modal?.params?.id) return;
+      .label {
+        flex-shrink: 0;
+        color: var(--sa-subfont);
+        font-size: 14px;
+        min-width: 80px;
+      }
 
-    loading.value = true;
-    try {
-      const { code, data } = await api.commission.detail(props.modal.params.id);
-      if (code == '200') {
-        commissionDetail.value = data;
+      .value {
+        color: var(--sa-subtitle);
+        font-size: 14px;
+        font-weight: 500;
       }
-    } catch (error) {
-      console.error('获取佣金详情失败:', error);
-    } finally {
-      loading.value = false;
     }
   }
 
-  // 获取操作日志
-  function getOperationLogs(page) {
-    if (page) operationLogs.pageData.page = page;
-    operationLogs.loading = true;
-
-    try {
-      // 模拟操作日志数据
-      const mockLogs = [
-        {
-          time: '2025/06/06 12:30:30',
-          operator: 'Aamir Khan',
-          action: '创建佣金记录',
-          remark: '--',
-        },
-        {
-          time: '2025/06/06 12:36:30',
-          operator: 'system',
-          action: '佣金状态更新为处理中',
-          remark: '--',
-        },
-        {
-          time: '2025/06/06 12:40:30',
-          operator: 'admin',
-          action: '佣金结算完成',
-          remark: '正常结算',
-        },
-      ];
-
-      operationLogs.data = mockLogs;
-      operationLogs.total = mockLogs.length;
-    } catch (error) {
-      console.error('获取操作日志失败:', error);
-    } finally {
-      operationLogs.loading = false;
+  .operation-logs {
+    h3 {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--sa-title);
     }
-  }
 
-  onMounted(() => {
-    getCommissionDetail();
-    getOperationLogs();
-  });
-</script>
+    .sa-table {
+      border-radius: 8px;
+      overflow: hidden;
+      border: 1px solid #dcdfe6;
 
-<style lang="scss" scoped>
-  .commission-detail {
-    .basic-info {
-      h3 {
-        font-size: 16px;
-        font-weight: 600;
-        color: var(--sa-title);
+      :deep(.el-table) {
+        border: none;
+        border-radius: 0;
       }
 
-      .info-item {
-        display: flex;
-        align-items: center;
-        margin-bottom: 8px;
+      :deep(.el-table__header-wrapper) {
+        background: var(--sa-table-header-bg, #f5f7fa);
 
-        .label {
-          flex-shrink: 0;
-          color: var(--sa-subfont);
-          font-size: 14px;
-          min-width: 80px;
-        }
-
-        .value {
-          color: var(--sa-subtitle);
-          font-size: 14px;
-          font-weight: 500;
-        }
-      }
-    }
+        .el-table__header {
+          background: var(--sa-table-header-bg, #f5f7fa);
 
-    .operation-logs {
-      h3 {
-        font-size: 16px;
-        font-weight: 600;
-        color: var(--sa-title);
-      }
+          th {
+            background: var(--sa-table-header-bg, #f5f7fa) !important;
+            font-weight: 600;
+            color: var(--sa-title, #303133);
+            border-bottom: 1px solid #dcdfe6;
+            border-right: 1px solid #dcdfe6;
 
-      .sa-table {
-        border-radius: 8px;
-        overflow: hidden;
-        border: 1px solid #dcdfe6;
+            &:first-child {
+              border-left: none;
+            }
 
-        :deep(.el-table) {
-          border: none;
-          border-radius: 0;
+            &:last-child {
+              border-right: none;
+            }
+          }
         }
+      }
 
-        :deep(.el-table__header-wrapper) {
-          background: var(--sa-table-header-bg, #f5f7fa);
-
-          .el-table__header {
-            background: var(--sa-table-header-bg, #f5f7fa);
+      :deep(.el-table__body-wrapper) {
+        .el-table__body {
+          tr {
+            &:hover {
+              background: #f5f7fa !important;
+            }
 
-            th {
-              background: var(--sa-table-header-bg, #f5f7fa) !important;
-              font-weight: 600;
-              color: var(--sa-title, #303133);
+            td {
               border-bottom: 1px solid #dcdfe6;
               border-right: 1px solid #dcdfe6;
+              border-left: none;
 
               &:first-child {
                 border-left: none;
@@ -328,37 +345,14 @@
                 border-right: none;
               }
             }
-          }
-        }
-
-        :deep(.el-table__body-wrapper) {
-          .el-table__body {
-            tr {
-              &:hover {
-                background: #f5f7fa !important;
-              }
-
-              td {
-                border-bottom: 1px solid #dcdfe6;
-                border-right: 1px solid #dcdfe6;
-                border-left: none;
-
-                &:first-child {
-                  border-left: none;
-                }
 
-                &:last-child {
-                  border-right: none;
-                }
-              }
-
-              &:last-child td {
-                border-bottom: none;
-              }
+            &:last-child td {
+              border-bottom: none;
             }
           }
         }
       }
     }
   }
+}
 </style>

+ 102 - 113
src/app/shop/admin/finance/commission/edit.vue

@@ -1,146 +1,135 @@
 <template>
   <el-container>
     <el-main>
-      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="100px">
-        <el-form-item label="用户ID" prop="user_id" v-if="!isView">
-          <el-input v-model="form.model.user_id" placeholder="请填写用户ID"></el-input>
+      <el-form :model="form.model" :rules="form.rules" ref="formRef" :label-width="formLabelWidth">
+        <el-form-item :label="t('modules.commission.userId')" prop="user_id" v-if="!isView">
+          <el-input v-model="form.model.user_id" :placeholder="t('modules.commission.enterUserId')"></el-input>
         </el-form-item>
-        <el-form-item label="用户信息" v-if="isView">
+        <el-form-item :label="t('modules.commission.userInfo')" v-if="isView">
           <div>
-            <p><strong>用户名:</strong>{{ form.model.username || '-' }}</p>
-            <p><strong>手机号:</strong>{{ form.model.mobile || '-' }}</p>
+            <p><strong>{{ t('modules.commission.userName') }}:</strong>{{ form.model.username || '-' }}</p>
+            <p><strong>{{ t('modules.commission.phoneNo') }}:</strong>{{ form.model.mobile || '-' }}</p>
           </div>
         </el-form-item>
-        <el-form-item label="佣金金额" prop="amount">
-          <el-input-number
-            v-model="form.model.amount"
-            :min="0"
-            :precision="2"
-            placeholder="佣金金额"
-            :disabled="isView"
-          />
+        <el-form-item :label="t('modules.commission.commissionAmount')" prop="amount">
+          <el-input-number v-model="form.model.amount" :min="0" :precision="2"
+            :placeholder="t('modules.commission.commissionAmount')" :disabled="isView" />
           <span style="margin-left: 10px">৳</span>
         </el-form-item>
-        <el-form-item label="佣金类型" prop="type">
-          <el-select v-model="form.model.type" placeholder="请选择佣金类型" :disabled="isView">
-            <el-option label="订单佣金" value="order"></el-option>
-            <el-option label="推荐佣金" value="referral"></el-option>
-            <el-option label="团队佣金" value="team"></el-option>
+        <el-form-item :label="t('modules.commission.commissionType')" prop="type">
+          <el-select v-model="form.model.type" :placeholder="t('modules.commission.selectCommissionType')"
+            :disabled="isView">
+            <el-option :label="t('modules.commission.orderCommission')" value="order"></el-option>
+            <el-option :label="t('modules.commission.referralCommission')" value="referral"></el-option>
+            <el-option :label="t('modules.commission.teamCommission')" value="team"></el-option>
           </el-select>
         </el-form-item>
-        <el-form-item label="来源订单" prop="source_order_no">
-          <el-input
-            v-model="form.model.source_order_no"
-            placeholder="请填写来源订单号"
-            :disabled="isView"
-          ></el-input>
+        <el-form-item :label="t('modules.commission.sourceOrder')" prop="source_order_no">
+          <el-input v-model="form.model.source_order_no" :placeholder="t('modules.commission.enterSourceOrderNo')"
+            :disabled="isView"></el-input>
         </el-form-item>
-        <el-form-item label="佣金比例" prop="commission_rate">
-          <el-input-number
-            v-model="form.model.commission_rate"
-            :min="0"
-            :max="100"
-            :precision="2"
-            placeholder="佣金比例"
-            :disabled="isView"
-          />
+        <el-form-item :label="t('modules.commission.commissionRate')" prop="commission_rate">
+          <el-input-number v-model="form.model.commission_rate" :min="0" :max="100" :precision="2"
+            :placeholder="t('modules.commission.commissionRate')" :disabled="isView" />
           <span style="margin-left: 10px">%</span>
         </el-form-item>
-        <el-form-item label="状态" prop="status">
-          <el-select v-model="form.model.status" placeholder="请选择状态" :disabled="isView">
-            <el-option label="待结算" value="pending"></el-option>
-            <el-option label="已结算" value="settled"></el-option>
-            <el-option label="已冻结" value="frozen"></el-option>
+        <el-form-item :label="t('common.status')" prop="status">
+          <el-select v-model="form.model.status" :placeholder="t('modules.commission.selectStatus')" :disabled="isView">
+            <el-option :label="t('modules.commission.unsettled')" value="pending"></el-option>
+            <el-option :label="t('modules.commission.settled')" value="settled"></el-option>
+            <el-option :label="t('modules.commission.frozen')" value="frozen"></el-option>
           </el-select>
         </el-form-item>
-        <el-form-item label="获得时间" v-if="isView">
+        <el-form-item :label="t('modules.commission.createTime')" v-if="isView">
           <span>{{ form.model.create_time || '-' }}</span>
         </el-form-item>
-        <el-form-item label="结算时间" v-if="isView && form.model.settle_time">
+        <el-form-item :label="t('modules.commission.settleTime')" v-if="isView && form.model.settle_time">
           <span>{{ form.model.settle_time || '-' }}</span>
         </el-form-item>
-        <el-form-item label="备注" prop="remark">
-          <el-input
-            v-model="form.model.remark"
-            type="textarea"
-            :rows="3"
-            placeholder="请填写备注"
-            :disabled="isView"
-          ></el-input>
+        <el-form-item :label="t('modules.commission.remark')" prop="remark">
+          <el-input v-model="form.model.remark" type="textarea" :rows="3"
+            :placeholder="t('modules.commission.enterRemark')" :disabled="isView"></el-input>
         </el-form-item>
       </el-form>
     </el-main>
     <el-footer class="sa-footer--submit" v-if="!isView">
-      <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, computed } from 'vue';
-  import { api } from '../finance.service';
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
+import { cloneDeep } from 'lodash';
+import { onMounted, reactive, ref, unref, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useFormConfig } from '@/hooks/useFormConfig';
+import { api } from '../finance.service';
 
-  const isView = computed(() => props.modal.params.type === 'view');
+const { t } = useI18n();
 
-  // 添加 编辑 form
-  let formRef = ref(null);
-  const form = reactive({
-    model: {
-      user_id: '',
-      username: '',
-      mobile: '',
-      amount: 0,
-      type: 'order',
-      source_order_no: '',
-      commission_rate: 0,
-      status: 'pending',
-      create_time: '',
-      settle_time: '',
-      remark: '',
-    },
-    rules: {
-      user_id: [{ required: true, message: '请填写用户ID', trigger: 'blur' }],
-      amount: [{ required: true, message: '请填写佣金金额', trigger: 'blur' }],
-      type: [{ required: true, message: '请选择佣金类型', trigger: 'change' }],
-      commission_rate: [{ required: true, message: '请填写佣金比例', trigger: 'blur' }],
-      status: [{ required: true, message: '请选择状态', trigger: 'change' }],
-    },
-  });
-  const loading = ref(false);
-  // 获取详情
-  async function getDetail(id) {
-    loading.value = true;
-    const { code, data } = await api.commission.detail(id);
-    code == '200' && (form.model = data);
-    loading.value = false;
-  }
-  // 表单关闭时提交
-  async function confirm() {
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-      let submitForm = cloneDeep(form.model);
-      const { code } =
-        props.modal.params.type == 'add'
-          ? await api.commission.add(submitForm)
-          : await api.commission.edit(props.modal.params.id, submitForm);
-      if (code == '200') {
-        emit('modalCallBack', { event: 'confirm' });
-      }
-    });
-  }
-  async function init() {
-    if (props.modal.params.id) {
-      await getDetail(props.modal.params.id);
+// 使用表单配置hooks
+const { formLabelWidth } = useFormConfig({ enWidth: '140px' });
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps({
+  modal: {
+    type: Object,
+  },
+});
+
+const isView = computed(() => props.modal.params.type === 'view');
+
+// 添加 编辑 form
+let formRef = ref(null);
+const form = reactive({
+  model: {
+    user_id: '',
+    username: '',
+    mobile: '',
+    amount: 0,
+    type: 'order',
+    source_order_no: '',
+    commission_rate: 0,
+    status: 'pending',
+    create_time: '',
+    settle_time: '',
+    remark: '',
+  },
+  rules: {
+    user_id: [{ required: true, message: '请填写用户ID', trigger: 'blur' }],
+    amount: [{ required: true, message: '请填写佣金金额', trigger: 'blur' }],
+    type: [{ required: true, message: '请选择佣金类型', trigger: 'change' }],
+    commission_rate: [{ required: true, message: '请填写佣金比例', trigger: 'blur' }],
+    status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+  },
+});
+const loading = ref(false);
+// 获取详情
+async function getDetail(id) {
+  loading.value = true;
+  const { code, data } = await api.commission.detail(id);
+  code == '200' && (form.model = data);
+  loading.value = false;
+}
+// 表单关闭时提交
+async function confirm() {
+  unref(formRef).validate(async (valid) => {
+    if (!valid) return;
+    let submitForm = cloneDeep(form.model);
+    const { code } =
+      props.modal.params.type == 'add'
+        ? await api.commission.add(submitForm)
+        : await api.commission.edit(props.modal.params.id, 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>

+ 242 - 249
src/app/shop/admin/finance/commission/index.vue

@@ -3,99 +3,80 @@
     <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>
       <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">佣金管理</div>
+        <div class="label sa-flex">{{ t('modules.commission.title') }}</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="exportRecords"
-          >
-            {{ exportLoading ? '导出中...' : '导出记录' }}
+          <el-button icon="Download" type="primary" :loading="exportLoading" :disabled="exportLoading"
+            @click="exportRecords">
+            {{ exportLoading ? t('modules.commission.exporting') : t('modules.commission.exportRecords') }}
           </el-button>
         </div>
       </div>
     </el-header>
     <el-main class="sa-p-0">
       <div class="sa-table-wrap panel-block panel-block--bottom" v-loading="loading">
-        <el-table
-          height="100%"
-          class="sa-table"
-          :data="table.data"
-          @sort-change="fieldFilter"
-          row-key="id"
-          stripe
-        >
+        <el-table height="100%" class="sa-table" :data="table.data" @sort-change="fieldFilter" row-key="id" stripe>
           <template #empty>
             <sa-empty />
           </template>
-          <el-table-column prop="tranNo" label="佣金单号" min-width="180" sortable="custom">
+          <el-table-column prop="tranNo" :label="t('modules.commission.commissionNo')" min-width="180"
+            sortable="custom">
             <template #default="scope">
               <span class="sa-table-line-1">{{ scope.row.tranNo || '-' }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="用户名" min-width="120">
+          <el-table-column :label="t('modules.commission.userName')" min-width="120">
             <template #default="scope">
               <el-link type="primary" :underline="true" @click="viewUserDetail(scope.row)">
                 {{ scope.row.name || '-' }}
               </el-link>
             </template>
           </el-table-column>
-          <el-table-column prop="phoneNo" label="手机号" min-width="130" align="center">
+          <el-table-column prop="phoneNo" :label="t('modules.commission.phoneNo')" min-width="130" align="center">
             <template #default="scope">
               {{ scope.row.phoneNo || '-' }}
             </template>
           </el-table-column>
-          <el-table-column label="佣金金额" min-width="120" align="center">
+          <el-table-column :label="t('modules.commission.commissionAmount')" min-width="120" align="center">
             <template #default="scope"> ৳{{ scope.row.amount || 0 }} </template>
           </el-table-column>
-          <el-table-column label="佣金类型" min-width="150">
+          <el-table-column :label="t('modules.commission.commissionType')" min-width="150">
             <template #default="scope">
               <span class="sa-table-line-1">{{ getBizTypeText(scope.row.bizType) }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="佣金说明" min-width="200">
+          <el-table-column :label="t('modules.commission.commissionDesc')" min-width="200">
             <template #default="scope">
               <span class="sa-table-line-1">{{ scope.row.memo || '-' }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="订单号" min-width="150">
+          <el-table-column :label="t('modules.commission.orderNo')" min-width="150">
             <template #default="scope">
-              <el-link
-                v-if="scope.row.orderNo"
-                type="primary"
-                :underline="true"
-                @click="viewOrderDetail(scope.row)"
-              >
+              <el-link v-if="scope.row.orderNo" type="primary" :underline="true" @click="viewOrderDetail(scope.row)">
                 {{ scope.row.orderNo }}
               </el-link>
               <span v-else>--</span>
             </template>
           </el-table-column>
-          <el-table-column label="状态" min-width="100" align="center">
+          <el-table-column :label="t('common.status')" min-width="100" align="center">
             <template #default="scope">
               <el-tag :type="getStatusType(scope.row.status)">
                 {{ getStatusText(scope.row.status) }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column prop="createTime" label="发放时间" min-width="160" sortable="custom">
+          <el-table-column prop="createTime" :label="t('modules.commission.issueTime')" min-width="160"
+            sortable="custom">
             <template #default="scope">
               {{ scope.row.createTime || '-' }}
             </template>
           </el-table-column>
-          <el-table-column label="到账时间" min-width="160">
+          <el-table-column :label="t('modules.commission.settleTime')" min-width="160">
             <template #default="scope">
               {{ scope.row.settleTime || '--' }}
             </template>
@@ -111,248 +92,260 @@
   </el-container>
 </template>
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import { api, financeUtils } from '../finance.service';
-  import { request } from '@/sheep/request';
-  import UserDetail from '../../user/list/detail.vue';
-  import OrderDetail from '../../order/order/detail.vue';
-  const { pageData } = usePagination();
+import { onMounted, reactive, ref, computed } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import { useModal, usePagination } from '@/sheep/hooks';
+import { api, financeUtils } from '../finance.service';
+import UserDetail from '../../user/list/detail.vue';
+import OrderDetail from '../../order/order/detail.vue';
 
-  // 搜索字段配置
-  const searchFields = reactive({
-    name: {
-      type: 'input',
-      label: '用户名',
-      placeholder: '请输入用户名',
-      width: 150,
-    },
-    phoneNo: {
-      type: 'input',
-      label: '手机号',
-      placeholder: '请输入手机号',
-      width: 150,
-    },
-    orderNo: {
-      type: 'input',
-      label: '订单号',
-      placeholder: '请输入订单号',
-      width: 180,
-    },
-    status: {
-      type: 'select',
-      label: '状态',
-      placeholder: '请选择状态',
-      width: 120,
-      options: [
-        { label: '全部', value: '' },
-        { label: '未结算', value: 1 },
-        { label: '已结算', value: 2 },
-      ],
-    },
-    bizType: {
-      type: 'select',
-      label: '佣金类型',
-      placeholder: '请选择类型',
-      width: 150,
-      options: [], // 动态加载
-    },
-    date_range: {
-      type: 'daterange',
-      label: '时间范围',
-      placeholder: '请选择时间范围',
-      width: 240,
-    },
-  });
+const { t } = useI18n();
+const { pageData } = usePagination();
 
-  // 默认搜索值
-  const defaultSearchValues = reactive({
-    name: '',
-    phoneNo: '',
-    orderNo: '',
-    status: '',
-    bizType: '',
-    date_range: [],
-  });
+// 业务类型选项 - 响应式数据
+const bizTypeOptions = ref([]);
 
-  // 当前搜索参数状态
-  const currentSearchParams = ref({});
+// 搜索字段配置 - 使用计算属性以支持语言切换
+const searchFields = computed(() => ({
+  name: {
+    type: 'input',
+    label: t('modules.commission.userName'),
+    placeholder: t('modules.commission.enterUserName'),
+    width: 150,
+  },
+  phoneNo: {
+    type: 'input',
+    label: t('modules.commission.phoneNo'),
+    placeholder: t('modules.commission.enterPhoneNo'),
+    width: 150,
+  },
+  orderNo: {
+    type: 'input',
+    label: t('modules.commission.orderNo'),
+    placeholder: t('modules.commission.enterOrderNo'),
+    width: 180,
+  },
+  status: {
+    type: 'select',
+    label: t('common.status'),
+    placeholder: t('modules.commission.selectStatus'),
+    width: 120,
+    options: [
+      { label: t('common.all'), value: '' },
+      { label: t('modules.commission.unsettled'), value: 1 },
+      { label: t('modules.commission.settled'), value: 2 },
+    ],
+  },
+  bizType: {
+    type: 'select',
+    label: t('modules.commission.commissionType'),
+    placeholder: t('modules.commission.selectType'),
+    width: 150,
+    options: bizTypeOptions.value, // 使用响应式的选项
+  },
+  date_range: {
+    type: 'daterange',
+    label: t('modules.finance.timeRange'),
+    placeholder: t('modules.commission.selectTimeRange'),
+    width: 240,
+  },
+}));
 
-  // 导出loading状态
-  const exportLoading = ref(false);
+// 默认搜索值
+const defaultSearchValues = reactive({
+  name: '',
+  phoneNo: '',
+  orderNo: '',
+  status: '',
+  bizType: '',
+  date_range: [],
+});
 
-  // 搜索处理函数
-  function handleSearch(searchParams) {
-    // 保存当前搜索参数
-    currentSearchParams.value = { ...searchParams };
-    // 执行搜索
-    getData(1, searchParams);
-  }
+// 当前搜索参数状态
+const currentSearchParams = ref({});
 
-  // 重置搜索参数处理函数
-  function handleReset() {
-    // 清空当前搜索参数
-    currentSearchParams.value = {};
-    // 执行重置
-    getData(1);
-  }
+// 导出loading状态
+const exportLoading = ref(false);
 
-  // 列表
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-  const loading = ref(true);
+// 搜索处理函数
+function handleSearch(searchParams) {
+  // 保存当前搜索参数
+  currentSearchParams.value = { ...searchParams };
+  // 执行搜索
+  getData(1, searchParams);
+}
 
-  // 使用service中的状态处理函数
-  const getStatusType = financeUtils.getCommissionStatusType;
-  const getStatusText = financeUtils.getCommissionStatusText;
-  const getBizTypeText = financeUtils.getBizTypeText;
+// 重置搜索参数处理函数
+function handleReset() {
+  // 清空当前搜索参数
+  currentSearchParams.value = {};
+  // 执行重置
+  getData(1);
+}
 
-  // 获取佣金类型枚举
-  async function getCommissionTypes() {
-    try {
-      const { code, data } = await request({
-        url: 'cif/api/user/getEnum',
-        method: 'GET',
-        params: { id: 5 },
-      });
+// 列表
+const table = reactive({
+  data: [],
+  order: '',
+  sort: '',
+  selected: [],
+});
+const loading = ref(true);
+
+// 状态处理函数
+const getStatusType = financeUtils.getCommissionStatusType;
+
+// 状态文本需要前端国际化(固定枚举值)
+const getStatusText = (status) => {
+  const statusMap = {
+    1: t('modules.commission.unsettled'),
+    2: t('modules.commission.settled'),
+  };
+  return statusMap[status] || t('modules.commission.unknown');
+};
+
+// 业务类型文本由后端处理国际化,这里保留原函数作为后备
+const getBizTypeText = financeUtils.getBizTypeText;
 
-      if (code === '200' && data) {
-        const options = [{ label: '全部', value: '' }];
-        data.forEach((item) => {
-          options.push({
-            label: item.name,
-            value: item.code,
-          });
+// 获取佣金类型枚举
+async function getCommissionTypes() {
+  try {
+    const { code, data } = await api.commission.getTypes();
+
+    if (code == '200' && data) {
+      const options = [{ label: t('common.all'), value: '' }]; // 前端国际化的"全部"选项
+      data.forEach((item) => {
+        options.push({
+          label: item.name, // 后端返回的已国际化文本
+          value: item.code,
         });
-        searchFields.bizType.options = options;
-      }
-    } catch (error) {
-      console.error('获取佣金类型枚举失败:', error);
+      });
+      bizTypeOptions.value = options;
     }
+  } catch (error) {
+    console.error('获取佣金类型枚举失败:', error);
   }
+}
 
-  // 获取数据
-  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;
 
-    try {
-      // 构建请求参数
-      let params = {
-        page: pageData.page,
-        size: pageData.size,
-        ...searchParams,
-      };
+  try {
+    // 构建请求参数
+    let params = {
+      page: pageData.page,
+      size: pageData.size,
+      ...searchParams,
+    };
 
-      // 处理日期范围
-      if (searchParams.date_range && searchParams.date_range.length === 2) {
-        params.startTime = searchParams.date_range[0];
-        params.endTime = searchParams.date_range[1];
-        delete params.date_range;
-      }
+    // 处理日期范围
+    if (searchParams.date_range && searchParams.date_range.length === 2) {
+      params.startTime = searchParams.date_range[0];
+      params.endTime = searchParams.date_range[1];
+      delete params.date_range;
+    }
 
-      // 调用真实API
-      const { code, data } = await api.commission.list(params, false);
+    // 调用真实API
+    const { code, data } = await api.commission.list(params, false);
 
-      if (code == '200') {
-        table.data = data.list || [];
-        pageData.page = data.pageNum || 1;
-        pageData.size = data.pageSize || 10;
-        pageData.total = data.total || 0;
-      }
-    } catch (error) {
-      console.error('获取数据失败:', error);
-      ElMessage.error('获取数据失败');
-    } finally {
-      loading.value = false;
+    if (code == '200') {
+      table.data = data.list || [];
+      pageData.page = data.pageNum || 1;
+      pageData.size = data.pageSize || 10;
+      pageData.total = data.total || 0;
     }
+  } catch (error) {
+    console.error('获取数据失败:', error);
+    ElMessage.error(t('common.fetchDataFailed'));
+  } finally {
+    loading.value = false;
   }
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
+}
+// table 字段排序
+function fieldFilter({ prop, order }) {
+  table.order = order == 'ascending' ? 'asc' : 'desc';
+  table.sort = prop;
+  getData();
+}
 
-  // 查看用户详情
-  function viewUserDetail(row) {
-    useModal(
-      UserDetail,
-      {
-        title: '用户详情',
-        type: 'view',
-        id: row.userId,
-      },
-      {
-        confirm: () => {
-          // 用户详情页面通常不需要确认回调
-        },
+// 查看用户详情
+function viewUserDetail(row) {
+  useModal(
+    UserDetail,
+    {
+      title: t('modules.commission.userDetail'),
+      type: 'view',
+      id: row.userId,
+    },
+    {
+      confirm: () => {
+        // 用户详情页面通常不需要确认回调
       },
-    );
-  }
+    },
+  );
+}
 
-  // 查看订单详情
-  function viewOrderDetail(row) {
-    if (!row.orderNo) return;
-    useModal(
-      OrderDetail,
-      {
-        title: '订单详情',
-        type: 'view',
-        id: row.orderNo,
-      },
-      {
-        confirm: () => {
-          // 订单详情页面通常不需要确认回调
-        },
+// 查看订单详情
+function viewOrderDetail(row) {
+  if (!row.orderNo) return;
+  useModal(
+    OrderDetail,
+    {
+      title: t('modules.commission.orderDetail'),
+      type: 'view',
+      id: row.orderNo,
+    },
+    {
+      confirm: () => {
+        // 订单详情页面通常不需要确认回调
       },
-    );
-  }
-
-  // 导出记录
-  async function exportRecords() {
-    if (exportLoading.value) return; // 防止重复点击
+    },
+  );
+}
 
-    exportLoading.value = true;
-    try {
-      // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
-      const exportParams = {
-        ...currentSearchParams.value, // 当前搜索参数
-      };
+// 导出记录
+async function exportRecords() {
+  if (exportLoading.value) return; // 防止重复点击
 
-      // 处理日期范围搜索(与 getData 函数保持一致)
-      if (exportParams.date_range && exportParams.date_range.length === 2) {
-        exportParams.startTime = exportParams.date_range[0];
-        exportParams.endTime = exportParams.date_range[1];
-        delete exportParams.date_range;
-      }
+  exportLoading.value = true;
+  try {
+    // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
+    const exportParams = {
+      ...currentSearchParams.value, // 当前搜索参数
+    };
 
-      // 调用导出API,所有下载逻辑都在REPORT函数中处理
-      await api.commission.report(exportParams, '佣金记录');
-    } finally {
-      exportLoading.value = false;
+    // 处理日期范围搜索(与 getData 函数保持一致)
+    if (exportParams.date_range && exportParams.date_range.length === 2) {
+      exportParams.startTime = exportParams.date_range[0];
+      exportParams.endTime = exportParams.date_range[1];
+      delete exportParams.date_range;
     }
+
+    // 调用导出API,所有下载逻辑都在REPORT函数中处理
+    await api.commission.report(exportParams, t('modules.commission.commissionRecords'));
+  } finally {
+    exportLoading.value = false;
   }
+}
 
-  onMounted(() => {
-    getCommissionTypes(); // 获取佣金类型枚举
-    getData();
-  });
+onMounted(() => {
+  getCommissionTypes(); // 获取佣金类型枚举
+  getData();
+});
 </script>
 <style lang="scss" scoped>
-  .commission-view {
-    .el-header {
-      height: auto;
-    }
-    .el-main {
-      .sa-table-wrap {
-        height: 100%;
-      }
+.commission-view {
+  .el-header {
+    height: auto;
+  }
+
+  .el-main {
+    .sa-table-wrap {
+      height: 100%;
     }
   }
+}
 </style>

+ 71 - 46
src/app/shop/admin/finance/finance.service.js

@@ -1,6 +1,10 @@
 import Content from '@/sheep/layouts/content.vue';
 import { SELECT, CRUD } from '@/sheep/request/crud';
 import { request } from '@/sheep/request';
+import i18n from '@/locales';
+
+// 获取 t 函数
+const { t } = i18n.global;
 
 // 财务模块公共配置
 const financeConfig = {
@@ -18,59 +22,71 @@ const financeConfig = {
   // 状态配置
   status: {
     recharge: {
-      1: '处理中',
-      2: '充值成功',
-      3: '充值失败',
-      4: '超时取消',
+      1: () => t('modules.recharge.processing'),
+      2: () => t('modules.recharge.success'),
+      3: () => t('modules.recharge.failed'),
+      4: () => t('modules.recharge.timeout'),
     },
     withdraw: {
-      1: '处理中',
-      2: '审核通过',
-      3: '审核拒绝',
-      4: '提现成功',
-      5: '提现失败',
-      6: '超时取消',
+      1: () => t('modules.withdraw.processing'),
+      2: () => t('modules.withdraw.approved'),
+      3: () => t('modules.withdraw.rejected'),
+      4: () => t('modules.withdraw.success'),
+      5: () => t('modules.withdraw.failed'),
+      6: () => t('modules.withdraw.timeout'),
     },
   },
 
   // 账户类型配置
   accountTypes: {
-    1: '钱包余额',
-    2: '账户收益',
+    1: () => t('modules.finance.walletBalance'),
+    2: () => t('modules.finance.accountEarnings'),
   },
 
   // 操作日志类型配置
   logTypes: {
-    1: '提交充值订单',
-    2: '充值调用第三方',
-    3: '充值回调通知成功',
-    4: '提交提现申请',
-    5: '提现审核通过',
-    6: '提现审核失败',
-    7: '提现调用第三方',
-    8: '提现回调通知成功',
+    1: () => t('modules.finance.submitRechargeOrder'),
+    2: () => t('modules.finance.rechargeCallThirdParty'),
+    3: () => t('modules.finance.rechargeCallbackSuccess'),
+    4: () => t('modules.finance.submitWithdrawApplication'),
+    5: () => t('modules.finance.withdrawApproved'),
+    6: () => t('modules.finance.withdrawRejected'),
+    7: () => t('modules.finance.withdrawCallThirdParty'),
+    8: () => t('modules.finance.withdrawCallbackSuccess'),
   },
 };
 
 // 公共工具函数
 const financeUtils = {
   // 获取渠道文本
-  getChannelText: (channel) => financeConfig.channels[channel] || '未知',
+  getChannelText: (channel) => financeConfig.channels[channel] || t('common.unknown'),
 
   // 获取币种文本
   getCurrencyText: (currency) => financeConfig.currencies[currency] || 'BDT',
 
   // 获取充值状态文本
-  getRechargeStatusText: (status) => financeConfig.status.recharge[status] || '未知',
+  getRechargeStatusText: (status) => {
+    const statusFn = financeConfig.status.recharge[status];
+    return statusFn ? statusFn() : t('common.unknown');
+  },
 
   // 获取提现状态文本
-  getWithdrawStatusText: (status) => financeConfig.status.withdraw[status] || '未知',
+  getWithdrawStatusText: (status) => {
+    const statusFn = financeConfig.status.withdraw[status];
+    return statusFn ? statusFn() : t('common.unknown');
+  },
 
   // 获取账户类型文本
-  getAccountTypeText: (accountType) => financeConfig.accountTypes[accountType] || '未知',
+  getAccountTypeText: (accountType) => {
+    const typeFn = financeConfig.accountTypes[accountType];
+    return typeFn ? typeFn() : t('common.unknown');
+  },
 
   // 获取操作日志类型文本
-  getLogTypeText: (type) => financeConfig.logTypes[type] || '未知操作',
+  getLogTypeText: (type) => {
+    const typeFn = financeConfig.logTypes[type];
+    return typeFn ? typeFn() : t('modules.finance.unknownOperation');
+  },
 
   // 获取状态类型(用于标签颜色)
   getStatusType: (status, type = 'withdraw') => {
@@ -107,10 +123,11 @@ const financeUtils = {
   // 佣金相关状态处理函数
   getCommissionStatusText: (status) => {
     const statusMap = {
-      1: '未结算',
-      2: '已结算',
+      1: () => t('modules.finance.unsettled'),
+      2: () => t('modules.finance.settled'),
     };
-    return statusMap[status] || '未知';
+    const statusFn = statusMap[status];
+    return statusFn ? statusFn() : t('common.unknown');
   },
 
   getCommissionStatusType: (status) => {
@@ -123,21 +140,22 @@ const financeUtils = {
 
   getBizTypeText: (bizType) => {
     const typeMap = {
-      1001: '充值',
-      2001: '提现',
-      3001: '开团支付',
-      3002: '参团支付',
-      4001: '未成团退款',
-      4002: '成团退款',
-      5001: '开团红包收益',
-      5002: '参团红包收益',
-      5003: '签到红包收益',
-      5004: '下级红包佣金',
-      5005: '下下级红包佣金',
-      6001: '充值返点',
-      7001: '提现手续费',
+      1001: () => t('modules.finance.recharge'),
+      2001: () => t('modules.finance.withdraw'),
+      3001: () => t('modules.finance.groupPayment'),
+      3002: () => t('modules.finance.joinPayment'),
+      4001: () => t('modules.finance.incompleteRefund'),
+      4002: () => t('modules.finance.completeRefund'),
+      5001: () => t('modules.finance.groupRedPacketEarnings'),
+      5002: () => t('modules.finance.joinRedPacketEarnings'),
+      5003: () => t('modules.finance.signInRedPacketEarnings'),
+      5004: () => t('modules.finance.subordinateRedPacketCommission'),
+      5005: () => t('modules.finance.subSubordinateRedPacketCommission'),
+      6001: () => t('modules.finance.rechargeRebate'),
+      7001: () => t('modules.finance.withdrawalFee'),
     };
-    return typeMap[bizType] || '未知类型';
+    const typeFn = typeMap[bizType];
+    return typeFn ? typeFn() : t('modules.finance.unknownType');
   },
 };
 
@@ -188,6 +206,13 @@ const api = {
   // 佣金相关 API
   commission: {
     ...CRUD('/cif/red/envelope', ['list', 'detail', 'report']),
+    // 获取佣金类型枚举
+    getTypes: () =>
+      request({
+        url: '/cif/api/user/getEnum',
+        method: 'GET',
+        params: { id: 5 },
+      }),
   },
 
   // 充值相关 API
@@ -202,12 +227,12 @@ const api = {
     select: (params) => SELECT('shop/admin/finance/recharge', params),
     confirm: (id) => ({
       error: 0,
-      msg: '确认成功',
+      msg: t('modules.finance.confirmSuccess'),
       data: null,
     }),
     batchConfirm: (ids) => ({
       error: 0,
-      msg: '批量确认成功',
+      msg: t('modules.finance.batchConfirmSuccess'),
       data: null,
     }),
   },
@@ -230,12 +255,12 @@ const api = {
       }),
     batchApprove: (ids) => ({
       error: 0,
-      msg: '批量通过成功',
+      msg: t('modules.finance.batchApproveSuccess'),
       data: null,
     }),
     batchReject: (ids, data) => ({
       error: 0,
-      msg: '批量拒绝成功',
+      msg: t('modules.finance.batchRejectSuccess'),
       data: null,
     }),
   },

+ 192 - 162
src/app/shop/admin/finance/recharge/detail.vue

@@ -1,121 +1,133 @@
 <template>
   <el-container class="recharge-detail">
-    <el-main v-loading="loading" element-loading-text="加载中...">
+    <el-main v-loading="loading" :element-loading-text="t('common.loading')">
       <!-- 基本信息 -->
       <div class="basic-info sa-m-b-26">
-        <h3 class="sa-m-b-20">基本信息</h3>
+        <h3 class="sa-m-b-20">{{ t('modules.recharge.basicInfo') }}</h3>
         <el-row :gutter="20">
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">充值单号:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.recharge.orderNo') }}:</span>
               <span class="value">{{ rechargeDetail.orderNo || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">充值状态:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.recharge.status') }}:</span>
               <el-tag :type="getStatusType(rechargeDetail.status)">
                 {{ getStatusText(rechargeDetail.status) }}
               </el-tag>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">充值银行:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.recharge.bank') }}:</span>
               <span class="value">{{ rechargeDetail.bankName || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">用户名:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.recharge.userName') }}:</span>
               <span class="value">{{ rechargeDetail.userName || '--' }}</span>
             </div>
           </el-col>
         </el-row>
         <el-row :gutter="20" class="sa-m-t-12">
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">账户名称:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.recharge.accountName') }}:</span>
               <span class="value">{{ rechargeDetail.bankAccountName || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">手机号:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.recharge.phoneNo') }}:</span>
               <span class="value">{{ rechargeDetail.userPhone || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">充值渠道:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.recharge.channel') }}:</span>
               <span class="value">{{ getChannelText(rechargeDetail.channel) }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">充值账户:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.recharge.account') }}:</span>
               <span class="value">{{ rechargeDetail.bankAccount || '--' }}</span>
             </div>
           </el-col>
         </el-row>
         <el-row :gutter="20" class="sa-m-t-12">
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">账户余额:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.recharge.method') }}:</span>
+              <span class="value">{{ getMethodText(rechargeDetail.method) }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.recharge.walletBalance') }}:</span>
               <span class="value">৳{{ rechargeDetail.walletBalance || 0 }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">币种:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.recharge.currency') }}:</span>
               <span class="value">{{ getCurrencyText(rechargeDetail.currency) }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">收益余额:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.recharge.earningsBalance') }}:</span>
               <span class="value">৳{{ rechargeDetail.earningsBalance || 0 }}</span>
             </div>
           </el-col>
+        </el-row>
+        <el-row :gutter="20" class="sa-m-t-12">
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">充值金额:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.recharge.amount') }}:</span>
               <span class="value">৳{{ rechargeDetail.amount || 0 }}</span>
             </div>
           </el-col>
+          <el-col :span="6">
+            <!-- 预留位置 -->
+          </el-col>
+          <el-col :span="6">
+            <!-- 预留位置 -->
+          </el-col>
+          <el-col :span="6">
+            <!-- 预留位置 -->
+          </el-col>
         </el-row>
       </div>
 
       <!-- 操作日志 -->
       <div class="operation-logs sa-m-b-26 mt-40px">
-        <h3 class="sa-m-b-20">操作日志</h3>
+        <h3 class="sa-m-b-20">{{ t('modules.recharge.operationLog') }}</h3>
         <div class="sa-table-wrap">
-          <el-table
-            class="sa-table"
-            :data="operationLogs.data"
-            v-loading="operationLogs.loading"
-            stripe
-            border
-          >
+          <el-table class="sa-table" :data="operationLogs.data" v-loading="operationLogs.loading" stripe border>
             <template #empty>
               <sa-empty />
             </template>
-            <el-table-column prop="createTime" label="时间" min-width="160" align="center">
+            <el-table-column prop="createTime" :label="t('modules.recharge.operationTime')" min-width="160"
+              align="center">
               <template #default="scope">
                 {{ scope.row.createTime || '--' }}
               </template>
             </el-table-column>
-            <el-table-column prop="createUser" label="操作人" min-width="120" align="center">
+            <el-table-column prop="createUser" :label="t('modules.recharge.operator')" min-width="120" align="center">
               <template #default="scope">
                 {{ scope.row.createUser || '--' }}
               </template>
             </el-table-column>
-            <el-table-column prop="type" label="事项" min-width="200">
+            <el-table-column prop="type" :label="t('modules.recharge.operationType')" min-width="200">
               <template #default="scope">
                 {{ getLogTypeText(scope.row.type) }}
               </template>
             </el-table-column>
-            <el-table-column prop="memo" label="备注" min-width="160" align="center">
+            <el-table-column prop="memo" :label="t('modules.recharge.remark')" min-width="160" align="center">
               <template #default="scope">
                 {{ scope.row.memo || '--' }}
               </template>
@@ -128,132 +140,173 @@
 </template>
 
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { usePagination } from '@/sheep/hooks';
-  import { api, financeUtils } from '../finance.service';
+import { onMounted, reactive, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { usePagination } from '@/sheep/hooks';
+import { useFormConfig } from '@/hooks/useFormConfig';
+import { api, financeUtils } from '../finance.service';
 
-  const props = defineProps({
-    modal: {
-      type: Object,
-      default: () => ({}),
-    },
-  });
+const { t } = useI18n();
 
-  const rechargeDetail = ref({});
-  const loading = ref(false);
+// 使用表单配置hooks来处理不同语言的宽度
+const { formLabelWidth } = useFormConfig({ enWidth: '150px' });
 
-  // 操作日志分页
-  const { pageData: logPageData } = usePagination();
-  const operationLogs = reactive({
-    data: [],
-    loading: false,
-    total: 0,
-    pageData: logPageData,
-  });
+const props = defineProps({
+  modal: {
+    type: Object,
+    default: () => ({}),
+  },
+});
 
-  // 使用公共配置的函数
-  const getStatusType = (status) => financeUtils.getStatusType(status, 'recharge');
-  const getStatusText = financeUtils.getRechargeStatusText;
-  const getChannelText = financeUtils.getChannelText;
-  const getCurrencyText = financeUtils.getCurrencyText;
-  const getLogTypeText = financeUtils.getLogTypeText;
+const rechargeDetail = ref({});
+const loading = ref(false);
 
-  // 获取充值详情
-  async function getRechargeDetail() {
-    if (!props.modal?.params?.id) return;
+// 操作日志分页
+const { pageData: logPageData } = usePagination();
+const operationLogs = reactive({
+  data: [],
+  loading: false,
+  total: 0,
+  pageData: logPageData,
+});
 
-    loading.value = true;
-    try {
-      const { code, data } = await api.recharge.recordDetail(props.modal.params.id);
-      if (code == '200') {
-        rechargeDetail.value = data;
-        // 获取详情后更新操作日志
-        getOperationLogs();
-      }
-    } catch (error) {
-      console.error('获取充值详情失败:', error);
-    } finally {
-      loading.value = false;
+// 使用公共配置的函数
+const getStatusType = (status) => financeUtils.getStatusType(status, 'recharge');
+const getStatusText = financeUtils.getRechargeStatusText;
+const getChannelText = financeUtils.getChannelText;
+const getCurrencyText = financeUtils.getCurrencyText;
+const getLogTypeText = financeUtils.getLogTypeText;
+
+// 充值方式文本函数(临时定义,等后端确认后更新)
+const getMethodText = (method) => {
+  const methodMap = {
+    1: t('modules.recharge.bankTransfer'),
+    2: t('modules.recharge.onlinePayment'),
+    3: t('modules.recharge.mobilePayment'),
+    4: t('modules.recharge.walletPayment'),
+  };
+  return methodMap[method] || t('common.unknown');
+};
+
+// 获取充值详情
+async function getRechargeDetail() {
+  if (!props.modal?.params?.id) return;
+
+  loading.value = true;
+  try {
+    const { code, data } = await api.recharge.recordDetail(props.modal.params.id);
+    if (code == '200') {
+      rechargeDetail.value = data;
+      // 获取详情后更新操作日志
+      getOperationLogs();
     }
+  } catch (error) {
+    console.error('获取充值详情失败:', error);
+  } finally {
+    loading.value = false;
   }
+}
 
-  // 获取操作日志 - 从详情数据中获取
-  function getOperationLogs() {
-    operationLogs.loading = true;
-    try {
-      // 使用详情数据中的 bizLogs
-      operationLogs.data = rechargeDetail.value.bizLogs || [];
-      operationLogs.total = operationLogs.data.length;
-    } catch (error) {
-      console.error('获取操作日志失败:', error);
-    } finally {
-      operationLogs.loading = false;
-    }
+// 获取操作日志 - 从详情数据中获取
+function getOperationLogs() {
+  operationLogs.loading = true;
+  try {
+    // 使用详情数据中的 bizLogs
+    operationLogs.data = rechargeDetail.value.bizLogs || [];
+    operationLogs.total = operationLogs.data.length;
+  } catch (error) {
+    console.error('获取操作日志失败:', error);
+  } finally {
+    operationLogs.loading = false;
   }
+}
 
-  onMounted(() => {
-    getRechargeDetail();
-  });
+onMounted(() => {
+  getRechargeDetail();
+});
 </script>
 
 <style lang="scss" scoped>
-  .recharge-detail {
-    .basic-info {
-      h3 {
-        font-size: 16px;
-        font-weight: 600;
-        color: var(--sa-title);
-      }
+.recharge-detail {
+  .basic-info {
+    h3 {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--sa-title);
+    }
 
-      .info-item {
-        display: flex;
-        align-items: center;
-        margin-bottom: 8px;
+    .info-item {
+      display: flex;
+      align-items: center;
+      margin-bottom: 8px;
 
-        .label {
-          flex-shrink: 0;
-          color: var(--sa-subfont);
-          font-size: 14px;
-          min-width: 80px;
-        }
+      .label {
+        flex-shrink: 0;
+        color: var(--sa-subfont);
+        font-size: 14px;
+        min-width: var(--label-width, 80px);
+      }
 
-        .value {
-          color: var(--sa-subtitle);
-          font-size: 14px;
-          font-weight: 500;
-        }
+      .value {
+        color: var(--sa-subtitle);
+        font-size: 14px;
+        font-weight: 500;
       }
     }
+  }
 
-    .operation-logs {
-      h3 {
-        font-size: 16px;
-        font-weight: 600;
-        color: var(--sa-title);
-      }
+  .operation-logs {
+    h3 {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--sa-title);
+    }
 
-      .sa-table {
-        border-radius: 8px;
-        overflow: hidden;
-        border: 1px solid #dcdfe6;
+    .sa-table {
+      border-radius: 8px;
+      overflow: hidden;
+      border: 1px solid #dcdfe6;
 
-        :deep(.el-table) {
-          border: none;
-          border-radius: 0;
-        }
+      :deep(.el-table) {
+        border: none;
+        border-radius: 0;
+      }
 
-        :deep(.el-table__header-wrapper) {
+      :deep(.el-table__header-wrapper) {
+        background: var(--sa-table-header-bg, #f5f7fa);
+
+        .el-table__header {
           background: var(--sa-table-header-bg, #f5f7fa);
 
-          .el-table__header {
-            background: var(--sa-table-header-bg, #f5f7fa);
+          th {
+            background: var(--sa-table-header-bg, #f5f7fa) !important;
+            font-weight: 600;
+            color: var(--sa-title, #303133);
+            border-bottom: 1px solid #dcdfe6;
+            border-right: 1px solid #dcdfe6;
+
+            &:first-child {
+              border-left: none;
+            }
+
+            &:last-child {
+              border-right: none;
+            }
+          }
+        }
+      }
 
-            th {
-              background: var(--sa-table-header-bg, #f5f7fa) !important;
-              font-weight: 600;
-              color: var(--sa-title, #303133);
+      :deep(.el-table__body-wrapper) {
+        .el-table__body {
+          tr {
+            &:hover {
+              background: #f5f7fa !important;
+            }
+
+            td {
               border-bottom: 1px solid #dcdfe6;
               border-right: 1px solid #dcdfe6;
+              border-left: none;
 
               &:first-child {
                 border-left: none;
@@ -263,37 +316,14 @@
                 border-right: none;
               }
             }
-          }
-        }
 
-        :deep(.el-table__body-wrapper) {
-          .el-table__body {
-            tr {
-              &:hover {
-                background: #f5f7fa !important;
-              }
-
-              td {
-                border-bottom: 1px solid #dcdfe6;
-                border-right: 1px solid #dcdfe6;
-                border-left: none;
-
-                &:first-child {
-                  border-left: none;
-                }
-
-                &:last-child {
-                  border-right: none;
-                }
-              }
-
-              &:last-child td {
-                border-bottom: none;
-              }
+            &:last-child td {
+              border-bottom: none;
             }
           }
         }
       }
     }
   }
+}
 </style>

+ 68 - 88
src/app/shop/admin/finance/recharge/edit.vue

@@ -3,28 +3,14 @@
     <el-main>
       <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="100px">
         <el-form-item label="用户ID" prop="user_id">
-          <el-input
-            v-model="form.model.user_id"
-            placeholder="请填写用户ID"
-            :disabled="isView"
-          ></el-input>
+          <el-input v-model="form.model.user_id" placeholder="请填写用户ID" :disabled="isView"></el-input>
         </el-form-item>
         <el-form-item label="充值金额" prop="amount">
-          <el-input-number
-            v-model="form.model.amount"
-            :min="0"
-            :precision="2"
-            placeholder="充值金额"
-            :disabled="isView"
-          />
+          <el-input-number v-model="form.model.amount" :min="0" :precision="2" placeholder="充值金额" :disabled="isView" />
           <span style="margin-left: 10px">৳</span>
         </el-form-item>
         <el-form-item label="支付方式" prop="payment_method">
-          <el-select
-            v-model="form.model.payment_method"
-            placeholder="请选择支付方式"
-            :disabled="isView"
-          >
+          <el-select v-model="form.model.payment_method" placeholder="请选择支付方式" :disabled="isView">
             <el-option label="支付宝" value="alipay"></el-option>
             <el-option label="微信支付" value="wechat"></el-option>
             <el-option label="银行卡" value="bank"></el-option>
@@ -32,11 +18,7 @@
           </el-select>
         </el-form-item>
         <el-form-item label="订单号" prop="order_no">
-          <el-input
-            v-model="form.model.order_no"
-            placeholder="请填写订单号"
-            :disabled="isView"
-          ></el-input>
+          <el-input v-model="form.model.order_no" placeholder="请填写订单号" :disabled="isView"></el-input>
         </el-form-item>
         <el-form-item label="状态" prop="status">
           <el-select v-model="form.model.status" placeholder="请选择状态" :disabled="isView">
@@ -46,82 +28,80 @@
           </el-select>
         </el-form-item>
         <el-form-item label="备注" prop="remark">
-          <el-input
-            v-model="form.model.remark"
-            type="textarea"
-            :rows="3"
-            placeholder="请填写备注"
-            :disabled="isView"
-          ></el-input>
+          <el-input v-model="form.model.remark" type="textarea" :rows="3" placeholder="请填写备注"
+            :disabled="isView"></el-input>
         </el-form-item>
       </el-form>
     </el-main>
     <el-footer class="sa-footer--submit" v-if="!isView">
-      <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, computed } from 'vue';
-  import { api } from '../finance.service';
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps({
-    modal: {
-      type: Object,
-    },
-  });
+import { cloneDeep } from 'lodash';
+import { onMounted, reactive, ref, unref, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { api } from '../finance.service';
 
-  const isView = computed(() => props.modal.params.type === 'view');
+const { t } = useI18n();
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps({
+  modal: {
+    type: Object,
+  },
+});
 
-  // 添加 编辑 form
-  let formRef = ref(null);
-  const form = reactive({
-    model: {
-      user_id: '',
-      amount: 0,
-      payment_method: 'alipay',
-      order_no: '',
-      status: 'pending',
-      remark: '',
-    },
-    rules: {
-      user_id: [{ required: true, message: '请填写用户ID', trigger: 'blur' }],
-      amount: [{ required: true, message: '请填写充值金额', trigger: 'blur' }],
-      payment_method: [{ required: true, message: '请选择支付方式', trigger: 'change' }],
-      order_no: [{ required: true, message: '请填写订单号', trigger: 'blur' }],
-      status: [{ required: true, message: '请选择状态', trigger: 'change' }],
-    },
-  });
-  const loading = ref(false);
-  // 获取详情
-  async function getDetail(id) {
-    loading.value = true;
-    const { code, data } = await api.recharge.detail(id);
-    code == '200' && (form.model = data);
-    loading.value = false;
-  }
-  // 表单关闭时提交
-  async function confirm() {
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-      let submitForm = cloneDeep(form.model);
-      const { code } =
-        props.modal.params.type == 'add'
-          ? await api.recharge.add(submitForm)
-          : await api.recharge.edit(props.modal.params.id, submitForm);
-      if (code == '200') {
-        emit('modalCallBack', { event: 'confirm' });
-      }
-    });
-  }
-  async function init() {
-    if (props.modal.params.id) {
-      await getDetail(props.modal.params.id);
+const isView = computed(() => props.modal.params.type === 'view');
+
+// 添加 编辑 form
+let formRef = ref(null);
+const form = reactive({
+  model: {
+    user_id: '',
+    amount: 0,
+    payment_method: 'alipay',
+    order_no: '',
+    status: 'pending',
+    remark: '',
+  },
+  rules: {
+    user_id: [{ required: true, message: '请填写用户ID', trigger: 'blur' }],
+    amount: [{ required: true, message: '请填写充值金额', trigger: 'blur' }],
+    payment_method: [{ required: true, message: '请选择支付方式', trigger: 'change' }],
+    order_no: [{ required: true, message: '请填写订单号', trigger: 'blur' }],
+    status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+  },
+});
+const loading = ref(false);
+// 获取详情
+async function getDetail(id) {
+  loading.value = true;
+  const { code, data } = await api.recharge.detail(id);
+  code == '200' && (form.model = data);
+  loading.value = false;
+}
+// 表单关闭时提交
+async function confirm() {
+  unref(formRef).validate(async (valid) => {
+    if (!valid) return;
+    let submitForm = cloneDeep(form.model);
+    const { code } =
+      props.modal.params.type == 'add'
+        ? await api.recharge.add(submitForm)
+        : await api.recharge.edit(props.modal.params.id, 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>

+ 287 - 270
src/app/shop/admin/finance/recharge/index.vue

@@ -3,103 +3,95 @@
     <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>
       <!-- 状态Tab切换 -->
       <el-tabs class="sa-tabs" v-model="currentStatus" @tab-change="handleTabChange">
-        <el-tab-pane label="全部" name="all"></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="充值失败" name="3"></el-tab-pane>
-        <el-tab-pane label="超时取消" name="4"></el-tab-pane>
+        <el-tab-pane :label="t('common.all')" name="all"></el-tab-pane>
+        <el-tab-pane :label="t('modules.recharge.processing')" name="1"></el-tab-pane>
+        <el-tab-pane :label="t('modules.recharge.success')" name="2"></el-tab-pane>
+        <el-tab-pane :label="t('modules.recharge.failed')" name="3"></el-tab-pane>
+        <el-tab-pane :label="t('modules.recharge.timeout')" name="4"></el-tab-pane>
       </el-tabs>
       <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">充值管理</div>
+        <div class="label sa-flex">{{ t('modules.recharge.title') }}</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="exportRecords"
-          >
-            {{ exportLoading ? '导出中...' : '导出记录' }}
+          <el-button icon="Download" type="primary" :loading="exportLoading" :disabled="exportLoading"
+            @click="exportRecords">
+            {{ exportLoading ? t('modules.recharge.exporting') : t('modules.recharge.exportRecords') }}
           </el-button>
         </div>
       </div>
     </el-header>
     <el-main class="sa-p-0">
       <div class="sa-table-wrap panel-block panel-block--bottom" v-loading="loading">
-        <el-table
-          height="100%"
-          class="sa-table"
-          :data="table.data"
-          @sort-change="fieldFilter"
-          row-key="id"
-          stripe
-        >
+        <el-table height="100%" class="sa-table" :data="table.data" @sort-change="fieldFilter" row-key="id" stripe>
           <template #empty>
             <sa-empty />
           </template>
-          <el-table-column prop="orderNo" label="订单号" min-width="180" sortable="custom">
+          <el-table-column prop="orderNo" :label="t('modules.recharge.orderNo')" min-width="180" sortable="custom">
             <template #default="scope">
               <span class="sa-table-line-1">{{ scope.row.orderNo || '-' }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="用户名" min-width="120">
+          <el-table-column :label="t('modules.recharge.userName')" min-width="120">
             <template #default="scope">
               <el-link type="primary" :underline="true" @click="viewUserDetail(scope.row)">
                 {{ scope.row.userName || '-' }}
               </el-link>
             </template>
           </el-table-column>
-          <el-table-column prop="userPhone" label="手机号" min-width="130" align="center">
+          <el-table-column prop="userPhone" :label="t('modules.recharge.phoneNo')" min-width="130" align="center">
             <template #default="scope">
               {{ scope.row.userPhone || '-' }}
             </template>
           </el-table-column>
-          <el-table-column label="充值渠道" min-width="100" align="center">
+          <el-table-column :label="t('modules.recharge.channel')" min-width="100" align="center">
             <template #default="scope">
               <el-tag :type="getChannelType(scope.row.channel)">
                 {{ getChannelText(scope.row.channel) }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="币种" min-width="80" align="center">
+          <el-table-column :label="t('modules.recharge.method')" min-width="100" align="center">
+            <template #default="scope">
+              <el-tag :type="getMethodType(scope.row.method)">
+                {{ getMethodText(scope.row.method) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('modules.recharge.currency')" min-width="80" align="center">
             <template #default="scope">
               {{ getCurrencyText(scope.row.currency) }}
             </template>
           </el-table-column>
-          <el-table-column label="金额" min-width="120" align="center">
+          <el-table-column :label="t('modules.recharge.amount')" min-width="120" align="center">
             <template #default="scope"> ৳{{ scope.row.amount || 0 }} </template>
           </el-table-column>
-          <el-table-column label="状态" min-width="100" align="center">
+          <el-table-column :label="t('common.status')" min-width="100" align="center">
             <template #default="scope">
               <el-tag :type="getStatusType(scope.row.status)">
                 {{ getStatusText(scope.row.status) }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column prop="createTime" label="下单时间" min-width="160" sortable="custom">
+          <el-table-column prop="createTime" :label="t('modules.recharge.createTime')" min-width="160"
+            sortable="custom">
             <template #default="scope">
               {{ scope.row.createTime || '-' }}
             </template>
           </el-table-column>
-          <el-table-column label="成功时间" min-width="160">
+          <el-table-column :label="t('modules.recharge.successTime')" min-width="160">
             <template #default="scope">
               {{ scope.row.successTime || '--' }}
             </template>
           </el-table-column>
-          <el-table-column fixed="right" label="操作" min-width="80">
+          <el-table-column fixed="right" :label="t('common.actions')" min-width="80">
             <template #default="scope">
-              <el-button link type="primary" @click="viewDetail(scope.row)">详情</el-button>
+              <el-button link type="primary" @click="viewDetail(scope.row)">{{ t('common.detail') }}</el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -113,260 +105,285 @@
   </el-container>
 </template>
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import { api, financeUtils } from '../finance.service';
-  import UserDetail from '../../user/list/detail.vue';
-  import RechargeDetail from './detail.vue';
-  const { pageData } = usePagination();
-
-  // 搜索字段配置
-  const searchFields = reactive({
-    userName: {
-      type: 'input',
-      label: '用户名',
-      placeholder: '请输入用户名',
-      width: 150,
-    },
-    userPhone: {
-      type: 'input',
-      label: '手机号',
-      placeholder: '请输入手机号',
-      width: 150,
-    },
-    orderNo: {
-      type: 'input',
-      label: '订单号',
-      placeholder: '请输入订单号',
-      width: 180,
-    },
-    channel: {
-      type: 'select',
-      label: '充值通道',
-      placeholder: '请选择通道',
-      width: 120,
-      options: [
-        { label: 'TKPAY', value: 1 },
-        { label: '3QPAY', value: 2 },
-      ],
-    },
-    timeType: {
-      type: 'select',
-      label: '时间类型',
-      placeholder: '请选择时间类型',
-      width: 120,
-      options: [
-        { label: '下单时间', value: 1 },
-        { label: '成功时间', value: 2 },
-      ],
-    },
-    date_range: {
-      type: 'daterange',
-      label: '时间范围',
-      placeholder: '请选择时间范围',
-      width: 400,
-    },
-  });
-
-  // 默认搜索值
-  const defaultSearchValues = reactive({
-    userName: '',
-    userPhone: '',
-    orderNo: '',
-    channel: '',
-    timeType: 1, // 默认下单时间
-    date_range: [],
-  });
-  // 当前状态标签
-  const currentStatus = ref('all');
-
-  // 当前搜索条件
-  const currentSearchParams = ref({});
-
-  // 导出loading状态
-  const exportLoading = ref(false);
-
-  // 列表
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-  const loading = ref(true);
-
-  // 使用公共配置的函数
-  const getStatusType = (status) => financeUtils.getStatusType(status, 'recharge');
-  const getStatusText = financeUtils.getRechargeStatusText;
-  const getChannelType = financeUtils.getChannelType;
-  const getChannelText = financeUtils.getChannelText;
-  const getCurrencyText = financeUtils.getCurrencyText;
-
-  // 获取数据
-  async function getData(page, searchParams = {}) {
-    if (page) pageData.page = page;
-    loading.value = true;
-
-    try {
-      // 构建请求参数
-      let params = {
-        page: pageData.page,
-        size: pageData.size,
-        ...searchParams,
-      };
-
-      // 处理日期范围
-      if (searchParams.date_range && searchParams.date_range.length === 2) {
-        params.startTime = searchParams.date_range[0];
-        params.endTime = searchParams.date_range[1];
-        delete params.date_range;
-      }
-
-      // 调用真实API
-      const { code, data } = await api.recharge.list(params, false);
-
-      if (code == '200') {
-        table.data = data.list || [];
-        pageData.page = data.pageNum || 1;
-        pageData.size = data.pageSize || 10;
-        pageData.total = data.total || 0;
-      }
-    } catch (error) {
-      console.error('获取数据失败:', error);
-      ElMessage.error('获取数据失败');
-    } finally {
-      loading.value = false;
-    }
-  }
-  // 标签切换处理
-  const handleTabChange = (status) => {
-    currentStatus.value = status;
+import { onMounted, reactive, ref, computed } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import { useModal, usePagination } from '@/sheep/hooks';
+import { api, financeUtils } from '../finance.service';
+import UserDetail from '../../user/list/detail.vue';
+import RechargeDetail from './detail.vue';
 
-    // 根据不同状态设置不同的筛选参数
-    let statusParams = {};
+const { t } = useI18n();
+const { pageData } = usePagination();
 
-    if (status !== 'all') {
-      statusParams.status = parseInt(status);
-    }
+// 搜索字段配置 - 使用计算属性以支持语言切换
+const searchFields = computed(() => ({
+  userName: {
+    type: 'input',
+    label: t('modules.recharge.userName'),
+    placeholder: t('modules.recharge.enterUserName'),
+    width: 150,
+  },
+  userPhone: {
+    type: 'input',
+    label: t('modules.recharge.phoneNo'),
+    placeholder: t('modules.recharge.enterPhoneNo'),
+    width: 150,
+  },
+  orderNo: {
+    type: 'input',
+    label: t('modules.recharge.orderNo'),
+    placeholder: t('modules.recharge.enterOrderNo'),
+    width: 180,
+  },
+  channel: {
+    type: 'select',
+    label: t('modules.recharge.channel'),
+    placeholder: t('modules.recharge.selectChannel'),
+    width: 120,
+    options: [
+      { label: 'TKPAY', value: 1 },
+      { label: '3QPAY', value: 2 },
+    ],
+  },
+  timeType: {
+    type: 'select',
+    label: t('modules.recharge.timeType'),
+    placeholder: t('modules.recharge.selectTimeType'),
+    width: 120,
+    options: [
+      { label: t('modules.recharge.createTime'), value: 1 },
+      { label: t('modules.recharge.successTime'), value: 2 },
+    ],
+  },
+  date_range: {
+    type: 'daterange',
+    label: t('modules.recharge.timeRange'),
+    placeholder: t('modules.recharge.selectTimeRange'),
+    width: 400,
+  },
+}));
 
-    // 整合搜索条件和状态筛选
-    const allParams = { ...currentSearchParams.value, ...statusParams };
-    getData(1, allParams);
-  };
+// 默认搜索值
+const defaultSearchValues = reactive({
+  userName: '',
+  userPhone: '',
+  orderNo: '',
+  channel: '',
+  timeType: 1, // 默认下单时间
+  date_range: [],
+});
+// 当前状态标签
+const currentStatus = ref('all');
 
-  // 搜索处理
-  const handleSearch = (searchParams) => {
-    currentSearchParams.value = searchParams;
+// 当前搜索条件
+const currentSearchParams = ref({});
 
-    // 获取当前tab的状态参数
-    let statusParams = {};
-    if (currentStatus.value !== 'all') {
-      statusParams.status = parseInt(currentStatus.value);
-    }
+// 导出loading状态
+const exportLoading = ref(false);
+
+// 列表
+const table = reactive({
+  data: [],
+  order: '',
+  sort: '',
+  selected: [],
+});
+const loading = ref(true);
 
-    // 整合搜索条件和状态筛选
-    const allParams = { ...searchParams, ...statusParams };
-    getData(1, allParams);
+// 使用公共配置的函数
+const getStatusType = (status) => financeUtils.getStatusType(status, 'recharge');
+const getStatusText = financeUtils.getRechargeStatusText;
+const getChannelType = financeUtils.getChannelType;
+const getChannelText = financeUtils.getChannelText;
+const getCurrencyText = financeUtils.getCurrencyText;
+
+// 充值方式相关函数(临时定义,等后端确认后更新)
+const getMethodType = (method) => {
+  const typeMap = {
+    1: 'primary',
+    2: 'success',
+    3: 'warning',
+    4: 'info',
   };
+  return typeMap[method] || '';
+};
 
-  // 重置处理
-  const handleReset = () => {
-    currentSearchParams.value = {};
+const getMethodText = (method) => {
+  const methodMap = {
+    1: t('modules.recharge.bankTransfer'),
+    2: t('modules.recharge.onlinePayment'),
+    3: t('modules.recharge.mobilePayment'),
+    4: t('modules.recharge.walletPayment'),
+  };
+  return methodMap[method] || t('common.unknown');
+};
 
-    // 获取当前tab的状态参数
-    let statusParams = {};
-    if (currentStatus.value !== 'all') {
-      statusParams.status = parseInt(currentStatus.value);
+// 获取数据
+async function getData(page, searchParams = {}) {
+  if (page) pageData.page = page;
+  loading.value = true;
+
+  try {
+    // 构建请求参数
+    let params = {
+      page: pageData.page,
+      size: pageData.size,
+      ...searchParams,
+    };
+
+    // 处理日期范围
+    if (searchParams.date_range && searchParams.date_range.length === 2) {
+      params.startTime = searchParams.date_range[0];
+      params.endTime = searchParams.date_range[1];
+      delete params.date_range;
     }
 
-    getData(1, statusParams);
-  };
+    // 调用真实API
+    const { code, data } = await api.recharge.list(params, false);
 
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
+    if (code == '200') {
+      table.data = data.list || [];
+      pageData.page = data.pageNum || 1;
+      pageData.size = data.pageSize || 10;
+      pageData.total = data.total || 0;
+    }
+  } catch (error) {
+    console.error('获取数据失败:', error);
+    ElMessage.error(t('common.fetchDataFailed'));
+  } finally {
+    loading.value = false;
   }
-  // 查看详情
-  function viewDetail(row) {
-    useModal(
-      RechargeDetail,
-      {
-        title: '充值详情',
-        type: 'view',
-        id: row.id,
-        size: '80%',
-      },
-      {
-        confirm: () => {
-          // 详情页面通常不需要确认回调
-        },
-      },
-    );
+}
+// 标签切换处理
+const handleTabChange = (status) => {
+  currentStatus.value = status;
+
+  // 根据不同状态设置不同的筛选参数
+  let statusParams = {};
+
+  if (status !== 'all') {
+    statusParams.status = parseInt(status);
   }
 
-  // 查看用户详情
-  function viewUserDetail(row) {
-    useModal(
-      UserDetail,
-      {
-        title: '用户详情',
-        type: 'view',
-        id: row.userId,
+  // 整合搜索条件和状态筛选
+  const allParams = { ...currentSearchParams.value, ...statusParams };
+  getData(1, allParams);
+};
+
+// 搜索处理
+const handleSearch = (searchParams) => {
+  currentSearchParams.value = searchParams;
+
+  // 获取当前tab的状态参数
+  let statusParams = {};
+  if (currentStatus.value !== 'all') {
+    statusParams.status = parseInt(currentStatus.value);
+  }
+
+  // 整合搜索条件和状态筛选
+  const allParams = { ...searchParams, ...statusParams };
+  getData(1, allParams);
+};
+
+// 重置处理
+const handleReset = () => {
+  currentSearchParams.value = {};
+
+  // 获取当前tab的状态参数
+  let statusParams = {};
+  if (currentStatus.value !== 'all') {
+    statusParams.status = parseInt(currentStatus.value);
+  }
+
+  getData(1, statusParams);
+};
+
+// table 字段排序
+function fieldFilter({ prop, order }) {
+  table.order = order == 'ascending' ? 'asc' : 'desc';
+  table.sort = prop;
+  getData();
+}
+// 查看详情
+function viewDetail(row) {
+  useModal(
+    RechargeDetail,
+    {
+      title: t('modules.recharge.detail'),
+      width: '1500px',
+      type: 'view',
+      id: row.id,
+    },
+    {
+      confirm: () => {
+        // 详情页面通常不需要确认回调
       },
-      {
-        confirm: () => {
-          // 用户详情页面通常不需要确认回调
-        },
+    },
+  );
+}
+
+// 查看用户详情
+function viewUserDetail(row) {
+  useModal(
+    UserDetail,
+    {
+      title: t('modules.recharge.userDetail'),
+      type: 'view',
+      id: row.userId,
+    },
+    {
+      confirm: () => {
+        // 用户详情页面通常不需要确认回调
       },
-    );
-  }
+    },
+  );
+}
 
-  // 导出记录
-  async function exportRecords() {
-    if (exportLoading.value) return; // 防止重复点击
-
-    exportLoading.value = true;
-    try {
-      // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
-      const exportParams = {
-        ...currentSearchParams.value, // 当前搜索参数
-      };
-
-      // 添加当前状态筛选
-      if (currentStatus.value !== 'all') {
-        exportParams.status = parseInt(currentStatus.value);
-      }
-
-      // 处理时间范围搜索(与 getData 函数保持一致)
-      if (exportParams.date_range && exportParams.date_range.length === 2) {
-        exportParams.startTime = exportParams.date_range[0];
-        exportParams.endTime = exportParams.date_range[1];
-        delete exportParams.date_range;
-      }
-
-      // 调用导出API,所有下载逻辑都在REPORT函数中处理
-      await api.recharge.report(exportParams, '充值记录');
-    } finally {
-      exportLoading.value = false;
+// 导出记录
+async function exportRecords() {
+  if (exportLoading.value) return; // 防止重复点击
+
+  exportLoading.value = true;
+  try {
+    // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
+    const exportParams = {
+      ...currentSearchParams.value, // 当前搜索参数
+    };
+
+    // 添加当前状态筛选
+    if (currentStatus.value !== 'all') {
+      exportParams.status = parseInt(currentStatus.value);
     }
+
+    // 处理时间范围搜索(与 getData 函数保持一致)
+    if (exportParams.date_range && exportParams.date_range.length === 2) {
+      exportParams.startTime = exportParams.date_range[0];
+      exportParams.endTime = exportParams.date_range[1];
+      delete exportParams.date_range;
+    }
+
+    // 调用导出API,所有下载逻辑都在REPORT函数中处理
+    await api.recharge.report(exportParams, t('modules.recharge.records'));
+  } finally {
+    exportLoading.value = false;
   }
+}
 
-  onMounted(() => {
-    getData();
-  });
+onMounted(() => {
+  getData();
+});
 </script>
 <style lang="scss" scoped>
-  .recharge-view {
-    .el-header {
-      height: auto;
-    }
-    .el-main {
-      .sa-table-wrap {
-        height: 100%;
-      }
+.recharge-view {
+  .el-header {
+    height: auto;
+  }
+
+  .el-main {
+    .sa-table-wrap {
+      height: 100%;
     }
   }
+}
 </style>

+ 274 - 300
src/app/shop/admin/finance/report/index.vue

@@ -3,68 +3,40 @@
     <el-header class="sa-header">
       <!-- 简化搜索组件 -->
       <div class="search-container">
-        <sa-search-simple
-          :searchFields="searchFields"
-          :defaultValues="defaultSearchValues"
-          @search="(val) => getData(1, val)"
-          @reset="refreshData"
-        >
+        <sa-search-simple :searchFields="searchFields" :defaultValues="defaultSearchValues"
+          @search="(val) => getData(1, val)" @reset="refreshData">
           <template #custom="{ data }">
             <el-form-item label="">
-              <el-date-picker
-                v-model="data.createTime"
-                type="daterange"
-                value-format="YYYY-MM-DD"
-                format="YYYY-MM-DD"
-                range-separator="至"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :editable="false"
-                :disabled-date="disabledDate"
-                style="width: 300px"
-              />
+              <el-date-picker v-model="data.createTime" type="daterange" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
+                :range-separator="t('common.to')" :start-placeholder="t('common.startDate')"
+                :end-placeholder="t('common.endDate')" :editable="false" :disabled-date="disabledDate"
+                style="width: 300px" />
             </el-form-item>
           </template>
         </sa-search-simple>
       </div>
       <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">财务报表</div>
+        <div class="label sa-flex">{{ t('modules.finance.financeReport') }}</div>
         <div>
           <el-button class="sa-button-refresh" icon="RefreshRight" @click="refreshData"></el-button>
-          <el-button
-            icon="Download"
-            type="primary"
-            :loading="exportLoading"
-            :disabled="exportLoading"
-            @click="exportReport"
-          >
-            {{ exportLoading ? '导出中...' : '导出报表' }}
+          <el-button icon="Download" type="primary" :loading="exportLoading" :disabled="exportLoading"
+            @click="exportReport">
+            {{ exportLoading ? t('modules.finance.exporting') : t('modules.finance.exportReport') }}
           </el-button>
         </div>
       </div>
     </el-header>
     <el-main class="sa-p-0">
       <div class="sa-table-wrap panel-block panel-block--bottom" v-loading="loading">
-        <el-table
-          height="100%"
-          class="sa-table"
-          :data="flatTableData"
-          @sort-change="fieldFilter"
-          stripe
-          border
-        >
+        <el-table height="100%" class="sa-table" :data="flatTableData" @sort-change="fieldFilter" stripe border>
           <template #empty>
             <sa-empty />
           </template>
-          <el-table-column label="序号" width="120" align="left">
+          <el-table-column :label="t('modules.finance.serialNumber')" width="120" align="left">
             <template #default="scope">
               <div class="subject-id-cell" :style="{ paddingLeft: scope.row.level * 20 + 'px' }">
                 <!-- 展开/收起按钮 -->
-                <el-icon
-                  v-if="scope.row.isParent"
-                  class="expand-icon"
-                  @click="toggleExpand(scope.row)"
-                >
+                <el-icon v-if="scope.row.isParent" class="expand-icon" @click="toggleExpand(scope.row)">
                   <ArrowRight v-if="!expandedRows.has(scope.row.subjectId)" />
                   <ArrowDown v-else />
                 </el-icon>
@@ -75,51 +47,47 @@
               </div>
             </template>
           </el-table-column>
-          <el-table-column prop="name" label="科目名称" min-width="200" sortable="custom">
+          <el-table-column prop="name" :label="t('modules.finance.subjectName')" min-width="200" sortable="custom">
             <template #default="scope">
               <span class="sa-table-line-1">
                 {{ scope.row.name || '-' }}
               </span>
             </template>
           </el-table-column>
-          <el-table-column
-            prop="aliases"
-            label="别名"
-            min-width="100"
-            align="center"
-            sortable="custom"
-          >
+          <el-table-column prop="aliases" :label="t('modules.finance.alias')" min-width="100" align="center"
+            sortable="custom">
             <template #default="scope">
               <span>{{ scope.row.aliases || '-' }}</span>
             </template>
           </el-table-column>
-          <el-table-column prop="direction" label="余额方向" min-width="100" align="center">
+          <el-table-column prop="direction" :label="t('modules.finance.balanceDirection')" min-width="100"
+            align="center">
             <template #default="scope">
               <el-tag :type="getDirectionType(scope.row.direction)" size="small">
                 {{ getDirectionText(scope.row.direction) }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column prop="initAmount" label="初始日余额" min-width="120" align="right">
+          <el-table-column prop="initAmount" :label="t('modules.finance.initialBalance')" min-width="120" align="right">
             <template #default="scope">
               <span class="amount-text">{{ formatAmount(scope.row.initAmount) }}</span>
             </template>
           </el-table-column>
-          <el-table-column prop="debitAmount" label="借方发生额" min-width="120" align="right">
+          <el-table-column prop="debitAmount" :label="t('modules.finance.debitAmount')" min-width="120" align="right">
             <template #default="scope">
               <span class="amount-text debit-amount">{{
                 formatAmount(scope.row.debitAmount)
               }}</span>
             </template>
           </el-table-column>
-          <el-table-column prop="creditAmount" label="贷方发生额" min-width="120" align="right">
+          <el-table-column prop="creditAmount" :label="t('modules.finance.creditAmount')" min-width="120" align="right">
             <template #default="scope">
               <span class="amount-text credit-amount">{{
                 formatAmount(scope.row.creditAmount)
               }}</span>
             </template>
           </el-table-column>
-          <el-table-column prop="endAmount" label="日余额" min-width="120" align="right">
+          <el-table-column prop="endAmount" :label="t('modules.finance.endBalance')" min-width="120" align="right">
             <template #default="scope">
               <span class="amount-text end-amount">{{ formatAmount(scope.row.endAmount) }}</span>
             </template>
@@ -134,279 +102,285 @@
 </template>
 
 <script setup>
-  import { onMounted, reactive, ref, computed } from 'vue';
-  import { ArrowRight, ArrowDown } from '@element-plus/icons-vue';
-  import { api } from '../finance.service';
-
-  // 当前搜索条件
-  const currentSearchParams = ref({});
-
-  // 导出loading状态
-  const exportLoading = ref(false);
-
-  // 列表
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-  const loading = ref(true);
-
-  // 搜索字段配置
-  const searchFields = reactive({
-    createTime: {
-      type: 'custom', // 标记为自定义字段
-      label: '时间范围',
-    },
-  });
-
-  // 获取默认时间范围(昨天一天)
-  const getDefaultDateRange = () => {
-    const yesterday = new Date();
-    yesterday.setDate(yesterday.getDate() - 1); // 昨天的日期
-    const yesterdayStr = yesterday.toISOString().split('T')[0]; // YYYY-MM-DD
-    return [
-      yesterdayStr, // 开始日期:昨天
-      yesterdayStr, // 结束日期:昨天
-    ];
-  };
-
-  // 禁用日期函数 - 禁用今天及以后的日期
-  const disabledDate = (time) => {
-    const today = new Date();
-    today.setHours(0, 0, 0, 0); // 设置为今天的开始时间
-    return time.getTime() >= today.getTime(); // 禁用今天及以后的日期
-  };
-
-  // 默认搜索值
-  const defaultSearchValues = reactive({
-    createTime: getDefaultDateRange(),
-  });
+import { onMounted, reactive, ref, computed } from 'vue';
+import { ArrowRight, ArrowDown } from '@element-plus/icons-vue';
+import { useI18n } from 'vue-i18n';
+import { api } from '../finance.service';
+
+const { t } = useI18n();
+
+// 当前搜索条件
+const currentSearchParams = ref({});
+
+// 导出loading状态
+const exportLoading = ref(false);
+
+// 列表
+const table = reactive({
+  data: [],
+  order: '',
+  sort: '',
+  selected: [],
+});
+const loading = ref(true);
+
+// 搜索字段配置
+const searchFields = reactive({
+  createTime: {
+    type: 'custom', // 标记为自定义字段
+    label: t('modules.finance.timeRange'),
+  },
+});
+
+// 获取默认时间范围(昨天一天)
+const getDefaultDateRange = () => {
+  const yesterday = new Date();
+  yesterday.setDate(yesterday.getDate() - 1); // 昨天的日期
+  const yesterdayStr = yesterday.toISOString().split('T')[0]; // YYYY-MM-DD
+  return [
+    yesterdayStr, // 开始日期:昨天
+    yesterdayStr, // 结束日期:昨天
+  ];
+};
+
+// 禁用日期函数 - 禁用今天及以后的日期
+const disabledDate = (time) => {
+  const today = new Date();
+  today.setHours(0, 0, 0, 0); // 设置为今天的开始时间
+  return time.getTime() >= today.getTime(); // 禁用今天及以后的日期
+};
+
+// 默认搜索值
+const defaultSearchValues = reactive({
+  createTime: getDefaultDateRange(),
+});
+
+// 展开状态管理
+const expandedRows = ref(new Set());
+
+// 切换展开状态
+const toggleExpand = (row) => {
+  if (expandedRows.value.has(row.subjectId)) {
+    expandedRows.value.delete(row.subjectId);
+  } else {
+    expandedRows.value.add(row.subjectId);
+  }
+};
+
+// 扁平化数据处理 - 根据展开状态决定是否显示子科目
+const flattenData = (data, level = 0) => {
+  const result = [];
+  data.forEach((item) => {
+    // 添加当前项,标记层级
+    result.push({
+      ...item,
+      level: level,
+      isParent: !!(item.children && item.children.length > 0),
+    });
 
-  // 展开状态管理
-  const expandedRows = ref(new Set());
+    // 如果有子项且当前项已展开,则显示子项(默认展开所有主科目)
+    if (item.children && item.children.length > 0) {
+      // 如果是主科目(level 0)且不在展开列表中,默认展开
+      if (level === 0 && !expandedRows.value.has(item.subjectId)) {
+        expandedRows.value.add(item.subjectId);
+      }
 
-  // 切换展开状态
-  const toggleExpand = (row) => {
-    if (expandedRows.value.has(row.subjectId)) {
-      expandedRows.value.delete(row.subjectId);
-    } else {
-      expandedRows.value.add(row.subjectId);
+      if (expandedRows.value.has(item.subjectId)) {
+        result.push(...flattenData(item.children, level + 1));
+      }
     }
+  });
+  return result;
+};
+
+// 计算属性 - 扁平化的表格数据
+const flatTableData = computed(() => {
+  return flattenData(table.data);
+});
+
+// 工具函数
+function getDirectionText(direction) {
+  const directionMap = {
+    D: t('modules.finance.debit'),
+    C: t('modules.finance.credit'),
   };
+  return directionMap[direction] || t('modules.finance.unknown');
+}
 
-  // 扁平化数据处理 - 根据展开状态决定是否显示子科目
-  const flattenData = (data, level = 0) => {
-    const result = [];
-    data.forEach((item) => {
-      // 添加当前项,标记层级
-      result.push({
-        ...item,
-        level: level,
-        isParent: !!(item.children && item.children.length > 0),
-      });
-
-      // 如果有子项且当前项已展开,则显示子项(默认展开所有主科目)
-      if (item.children && item.children.length > 0) {
-        // 如果是主科目(level 0)且不在展开列表中,默认展开
-        if (level === 0 && !expandedRows.value.has(item.subjectId)) {
-          expandedRows.value.add(item.subjectId);
-        }
-
-        if (expandedRows.value.has(item.subjectId)) {
-          result.push(...flattenData(item.children, level + 1));
-        }
-      }
-    });
-    return result;
+function getDirectionType(direction) {
+  const typeMap = {
+    D: 'success', // 借方用绿色
+    C: 'warning', // 贷方用橙色
   };
-
-  // 计算属性 - 扁平化的表格数据
-  const flatTableData = computed(() => {
-    return flattenData(table.data);
+  return typeMap[direction] || 'info';
+}
+
+function formatAmount(amount) {
+  if (amount === null || amount === undefined) return '0.00';
+  // 根据当前语言环境格式化金额
+  const locale = t('locale') === 'zh-CN' ? 'zh-CN' : 'en-US';
+  return Number(amount).toLocaleString(locale, {
+    minimumFractionDigits: 2,
+    maximumFractionDigits: 2,
   });
-
-  // 工具函数
-  function getDirectionText(direction) {
-    const directionMap = {
-      D: '借',
-      C: '贷',
-    };
-    return directionMap[direction] || '未知';
+}
+
+// 刷新数据 - 使用默认时间范围
+const refreshData = () => {
+  // 重置搜索参数为默认值
+  currentSearchParams.value = { ...defaultSearchValues };
+  // 使用默认时间范围刷新数据
+  getData(1, defaultSearchValues);
+};
+
+// table 字段排序
+function fieldFilter({ prop, order }) {
+  table.order = order == 'ascending' ? 'asc' : 'desc';
+  table.sort = prop;
+  getData();
+}
+
+// 获取数据
+async function getData(page = 1, searchParams = {}) {
+  // 保存搜索条件供导出使用
+  if (Object.keys(searchParams).length > 0) {
+    currentSearchParams.value = { ...searchParams };
   }
+  loading.value = true;
 
-  function getDirectionType(direction) {
-    const typeMap = {
-      D: 'success', // 借方用绿色
-      C: 'warning', // 贷方用橙色
+  try {
+    // 构建请求参数
+    const params = {
+      ...searchParams,
     };
-    return typeMap[direction] || 'info';
-  }
-
-  function formatAmount(amount) {
-    if (amount === null || amount === undefined) return '0.00';
-    return Number(amount).toLocaleString('zh-CN', {
-      minimumFractionDigits: 2,
-      maximumFractionDigits: 2,
-    });
-  }
-
-  // 刷新数据 - 使用默认时间范围
-  const refreshData = () => {
-    // 重置搜索参数为默认值
-    currentSearchParams.value = { ...defaultSearchValues };
-    // 使用默认时间范围刷新数据
-    getData(1, defaultSearchValues);
-  };
-
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
 
-  // 获取数据
-  async function getData(page = 1, searchParams = {}) {
-    // 保存搜索条件供导出使用
-    if (Object.keys(searchParams).length > 0) {
-      currentSearchParams.value = { ...searchParams };
+    // 添加排序参数
+    if (table.order && table.sort) {
+      params.order = table.order;
+      params.sort = table.sort;
     }
-    loading.value = true;
-
-    try {
-      // 构建请求参数
-      const params = {
-        ...searchParams,
-      };
-
-      // 添加排序参数
-      if (table.order && table.sort) {
-        params.order = table.order;
-        params.sort = table.sort;
-      }
 
-      // 处理时间范围搜索,直接使用年月日格式
-      if (searchParams.createTime && searchParams.createTime.length === 2) {
-        params.startTime = searchParams.createTime[0];
-        params.endTime = searchParams.createTime[1];
-        delete params.createTime;
-      }
+    // 处理时间范围搜索,直接使用年月日格式
+    if (searchParams.createTime && searchParams.createTime.length === 2) {
+      params.startTime = searchParams.createTime[0];
+      params.endTime = searchParams.createTime[1];
+      delete params.createTime;
+    }
 
-      // 调用API
-      const { code, data } = await api.report.list(params);
+    // 调用API
+    const { code, data } = await api.report.list(params);
 
-      if (code == '200') {
-        table.data = data || [];
-      }
-    } catch (error) {
-      table.data = [];
-    } finally {
-      loading.value = false;
+    if (code == '200') {
+      table.data = data || [];
     }
+  } catch (error) {
+    table.data = [];
+  } finally {
+    loading.value = false;
   }
+}
 
-  // 导出报表
-  async function exportReport() {
-    if (exportLoading.value) return; // 防止重复点击
-
-    exportLoading.value = true;
-    try {
-      // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
-      const exportParams = {
-        ...currentSearchParams.value, // 当前搜索参数
-      };
-
-      // 处理时间范围搜索(与 getData 函数保持一致),直接使用年月日格式
-      if (exportParams.createTime && exportParams.createTime.length === 2) {
-        exportParams.startTime = exportParams.createTime[0];
-        exportParams.endTime = exportParams.createTime[1];
-        delete exportParams.createTime;
-      }
+// 导出报表
+async function exportReport() {
+  if (exportLoading.value) return; // 防止重复点击
 
-      // 生成包含时间范围的文件名作为兜底方案
-      let fileName = '财务报表';
-      if (exportParams.startTime && exportParams.endTime) {
-        if (exportParams.startTime === exportParams.endTime) {
-          // 单日报表
-          fileName = `财务报表_${exportParams.startTime}`;
-        } else {
-          // 时间范围报表
-          fileName = `财务报表_${exportParams.startTime}_至_${exportParams.endTime}`;
-        }
-      }
+  exportLoading.value = true;
+  try {
+    // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
+    const exportParams = {
+      ...currentSearchParams.value, // 当前搜索参数
+    };
 
-      // 调用导出API,优先使用服务器返回的文件名,备用使用生成的文件名
-      await api.report.export(exportParams, fileName);
-    } finally {
-      exportLoading.value = false;
+    // 处理时间范围搜索(与 getData 函数保持一致),直接使用年月日格式
+    if (exportParams.createTime && exportParams.createTime.length === 2) {
+      exportParams.startTime = exportParams.createTime[0];
+      exportParams.endTime = exportParams.createTime[1];
+      delete exportParams.createTime;
     }
-  }
-
-  onMounted(() => {
-    // 设置当前搜索参数为默认值,确保搜索组件显示默认时间
-    currentSearchParams.value = { ...defaultSearchValues };
-    // 使用默认时间范围进行初始查询
-    getData(1, defaultSearchValues);
-  });
-</script>
-
-<style scoped>
-  /* 表格样式优化 */
-  :deep(.el-table) {
-    .el-table__row {
-      /* 主科目行样式 */
-      &[data-level='0'] {
-        background-color: #fafafa;
-        font-weight: 500;
-      }
 
-      /* 子科目行样式 */
-      &[data-level='1'] {
-        background-color: #ffffff;
+    // 生成包含时间范围的文件名作为兜底方案
+    let fileName = t('modules.finance.financeReport');
+    if (exportParams.startTime && exportParams.endTime) {
+      if (exportParams.startTime === exportParams.endTime) {
+        // 单日报表
+        fileName = `${t('modules.finance.financeReport')}_${exportParams.startTime}`;
+      } else {
+        // 时间范围报表
+        fileName = `${t('modules.finance.financeReport')}_${exportParams.startTime}_${t('common.to')}_${exportParams.endTime}`;
       }
     }
-  }
-
-  /* 序号单元格样式 */
-  .subject-id-cell {
-    display: flex;
-    align-items: center;
-  }
 
-  /* 展开按钮样式 */
-  .expand-icon {
-    cursor: pointer;
-    margin-right: 8px;
-    width: 16px;
-    height: 16px;
-    color: #606266;
-    transition: color 0.3s;
-    flex-shrink: 0;
-  }
-
-  .expand-icon:hover {
-    color: #409eff;
-  }
-
-  /* 展开按钮占位符 */
-  .expand-placeholder {
-    width: 24px;
-    height: 16px;
-    margin-right: 8px;
+    // 调用导出API,优先使用服务器返回的文件名,备用使用生成的文件名
+    await api.report.export(exportParams, fileName);
+  } finally {
+    exportLoading.value = false;
   }
+}
+
+onMounted(() => {
+  // 设置当前搜索参数为默认值,确保搜索组件显示默认时间
+  currentSearchParams.value = { ...defaultSearchValues };
+  // 使用默认时间范围进行初始查询
+  getData(1, defaultSearchValues);
+});
+</script>
 
-  /* 序号样式 */
-  .subject-id {
-    flex: 1;
-    font-weight: 500;
-  }
+<style scoped>
+/* 表格样式优化 */
+:deep(.el-table) {
+  .el-table__row {
+
+    /* 主科目行样式 */
+    &[data-level='0'] {
+      background-color: #fafafa;
+      font-weight: 500;
+    }
 
-  /* 为子科目序号添加连接线效果 */
-  .subject-id-cell[style*='padding-left: 20px'] .subject-id::before {
-    content: '├─';
-    color: #dcdfe6;
-    margin-right: 4px;
+    /* 子科目行样式 */
+    &[data-level='1'] {
+      background-color: #ffffff;
+    }
   }
+}
+
+/* 序号单元格样式 */
+.subject-id-cell {
+  display: flex;
+  align-items: center;
+}
+
+/* 展开按钮样式 */
+.expand-icon {
+  cursor: pointer;
+  margin-right: 8px;
+  width: 16px;
+  height: 16px;
+  color: #606266;
+  transition: color 0.3s;
+  flex-shrink: 0;
+}
+
+.expand-icon:hover {
+  color: #409eff;
+}
+
+/* 展开按钮占位符 */
+.expand-placeholder {
+  width: 24px;
+  height: 16px;
+  margin-right: 8px;
+}
+
+/* 序号样式 */
+.subject-id {
+  flex: 1;
+  font-weight: 500;
+}
+
+/* 为子科目序号添加连接线效果 */
+.subject-id-cell[style*='padding-left: 20px'] .subject-id::before {
+  content: '├─';
+  color: #dcdfe6;
+  margin-right: 4px;
+}
 </style>

+ 76 - 79
src/app/shop/admin/finance/withdraw/audit.vue

@@ -11,100 +11,97 @@
         </el-form-item> -->
 
         <!-- 审核内容 -->
-        <el-form-item :label="auditType === 'approve' ? '审核备注:' : '拒绝原因:'" prop="review">
-          <el-input
-            v-model="formData.review"
-            type="textarea"
-            :rows="4"
-            :placeholder="
-              auditType === 'approve' ? '请输入审核备注(可选)' : '请输入拒绝原因(必填)'
-            "
-            :maxlength="200"
-            show-word-limit
-          />
+        <el-form-item
+          :label="auditType === 'approve' ? t('modules.withdraw.auditRemark') + ':' : t('modules.withdraw.rejectReason') + ':'"
+          prop="review">
+          <el-input v-model="formData.review" type="textarea" :rows="4" :placeholder="auditType === 'approve' ? t('modules.withdraw.enterAuditRemark') : t('modules.withdraw.enterRejectReason')
+            " :maxlength="200" show-word-limit />
         </el-form-item>
       </el-form>
     </el-main>
     <el-footer class="sa-footer--submit">
-      <el-button @click="handleCancel">取消</el-button>
-      <el-button type="primary" @click="handleConfirm" :loading="loading">提交</el-button>
+      <el-button @click="handleCancel">{{ t('common.cancel') }}</el-button>
+      <el-button type="primary" @click="handleConfirm" :loading="loading">{{ t('common.submit') }}</el-button>
     </el-footer>
   </el-container>
 </template>
 
 <script setup>
-  import { reactive, ref, computed } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import { api } from '../finance.service';
-
-  const props = defineProps(['modal']);
-  const emit = defineEmits(['modalCallBack']);
-
-  const formRef = ref();
-  const loading = ref(false);
-
-  // 审核类型:approve(通过) 或 reject(拒绝)
-  const auditType = computed(() => props.modal?.params?.type || 'approve');
-
-  // 表单数据
-  const formData = reactive({
-    review: '', // 审核内容
-  });
-
-  // 表单验证规则
-  const rules = computed(() => {
-    const baseRules = {};
-
-    if (auditType.value === 'reject') {
-      // 拒绝时必须填写原因
-      baseRules.review = [
-        { required: true, message: '请输入拒绝原因', trigger: 'blur' },
-        { min: 1, max: 200, message: '拒绝原因长度在1到200个字符', trigger: 'blur' },
-      ];
-    }
+import { reactive, ref, computed } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import { api } from '../finance.service';
+
+const { t } = useI18n();
+
+const props = defineProps(['modal']);
+const emit = defineEmits(['modalCallBack']);
+
+const formRef = ref();
+const loading = ref(false);
 
-    return baseRules;
-  });
+// 审核类型:approve(通过) 或 reject(拒绝)
+const auditType = computed(() => props.modal?.params?.type || 'approve');
 
-  // 取消操作
-  function handleCancel() {
-    emit('modalCallBack', { action: 'cancel' });
+// 表单数据
+const formData = reactive({
+  review: '', // 审核内容
+});
+
+// 表单验证规则
+const rules = computed(() => {
+  const baseRules = {};
+
+  if (auditType.value === 'reject') {
+    // 拒绝时必须填写原因
+    baseRules.review = [
+      { required: true, message: t('modules.withdraw.rejectReasonRequired'), trigger: 'blur' },
+      { min: 1, max: 200, message: t('modules.withdraw.rejectReasonLength'), trigger: 'blur' },
+    ];
   }
 
-  // 确认操作
-  async function handleConfirm() {
-    if (!formRef.value) return;
-
-    try {
-      const valid = await formRef.value.validate();
-      if (!valid) return;
-
-      loading.value = true;
-
-      // 构建请求参数
-      const requestData = {
-        id: props.modal?.params?.id,
-        type: auditType.value === 'approve' ? 1 : 2, // 1通过 2不通过
-        review: formData.review || '', // 审核内容
-      };
-
-      // 调用审核API
-      const { code, message } = await api.withdraw.review(requestData);
-
-      if (code === '200') {
-        const successMessage = auditType.value === 'approve' ? '审核通过成功' : '审核拒绝成功';
-        ElMessage.success(successMessage);
-
-        // 返回审核结果
-        emit('modalCallBack', { event: 'confirm' });
-      } else {
-        throw new Error(message || '审核失败');
-      }
-    } catch (error) {
-    } finally {
-      loading.value = false;
+  return baseRules;
+});
+
+// 取消操作
+function handleCancel() {
+  emit('modalCallBack', { action: 'cancel' });
+}
+
+// 确认操作
+async function handleConfirm() {
+  if (!formRef.value) return;
+
+  try {
+    const valid = await formRef.value.validate();
+    if (!valid) return;
+
+    loading.value = true;
+
+    // 构建请求参数
+    const requestData = {
+      id: props.modal?.params?.id,
+      type: auditType.value === 'approve' ? 1 : 2, // 1通过 2不通过
+      review: formData.review || '', // 审核内容
+    };
+
+    // 调用审核API
+    const { code, message } = await api.withdraw.review(requestData);
+
+    if (code === '200') {
+      const successMessage = auditType.value === 'approve' ? t('modules.withdraw.approveSuccess') : t('modules.withdraw.rejectSuccess');
+      ElMessage.success(successMessage);
+
+      // 返回审核结果
+      emit('modalCallBack', { event: 'confirm' });
+    } else {
+      throw new Error(message || t('modules.withdraw.auditFailed'));
     }
+  } catch (error) {
+  } finally {
+    loading.value = false;
   }
+}
 </script>
 
 <style lang="scss" scoped></style>

+ 248 - 220
src/app/shop/admin/finance/withdraw/detail.vue

@@ -1,70 +1,69 @@
 <template>
   <el-container class="withdraw-detail">
-    <el-main v-loading="loading" element-loading-text="加载中...">
+    <el-main v-loading="loading" :element-loading-text="t('common.loading')">
       <!-- 基本信息 -->
       <div class="basic-info sa-m-b-26">
         <div class="sa-flex sa-row-between sa-m-b-20">
-          <h3>基本信息</h3>
+          <h3>{{ t('modules.withdraw.basicInfo') }}</h3>
           <!-- 操作按钮 -->
           <div class="operation-buttons mb-40px" v-if="withdrawDetail.status === 1">
-            <el-button type="success" plain @click="handleApprove" class="mr-10px"
-              >审核通过</el-button
-            >
-            <el-button type="danger" plain @click="handleReject">审核拒绝</el-button>
+            <el-button type="success" plain @click="handleApprove" class="mr-10px">{{ t('modules.withdraw.approve')
+              }}</el-button>
+            <el-button type="danger" plain @click="handleReject">{{ t('modules.withdraw.reject') }}</el-button>
           </div>
         </div>
         <el-row :gutter="20">
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">提款单号:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.orderNo') }}:</span>
               <span class="value">{{ withdrawDetail.orderNo || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">提款状态:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.status') }}:</span>
               <el-tag :type="getStatusType(withdrawDetail.status)">
                 {{ getStatusText(withdrawDetail.status) }}
               </el-tag>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">收款银行:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.receivingBank') }}:</span>
               <span class="value">{{ withdrawDetail.bank || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">用户名:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.userName') }}:</span>
               <span class="value">{{ withdrawDetail.userName || '--' }}</span>
             </div>
           </el-col>
         </el-row>
         <el-row :gutter="20" class="sa-m-t-12">
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">提款类型:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.accountType') }}:</span>
               <el-tag :type="withdrawDetail.accountType === 1 ? 'primary' : 'success'">
-                {{ getAccountTypeText(withdrawDetail.accountType) }}
+                {{ getAccountTypeTextLocal(withdrawDetail.accountType) }}
               </el-tag>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">账户名称:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.accountName') }}:</span>
               <span class="value">{{ withdrawDetail.bankAccountName || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">手机号:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.phoneNo') }}:</span>
               <span class="value">{{ withdrawDetail.userPhone || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">提款渠道:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.channel') }}:</span>
               <el-tag :type="getChannelType(withdrawDetail.channel)">
                 {{ getChannelText(withdrawDetail.channel) }}
               </el-tag>
@@ -73,76 +72,77 @@
         </el-row>
         <el-row :gutter="20" class="sa-m-t-12">
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">收款账户:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.method') }}:</span>
+              <span class="value">{{ getMethodText(withdrawDetail.method) }}</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.receivingAccount') }}:</span>
               <span class="value">{{ withdrawDetail.bankAccount || '--' }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">账户余额:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.walletBalance') }}:</span>
               <span class="value">৳{{ withdrawDetail.walletBalance || 0 }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">币种:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.currency') }}:</span>
               <span class="value">{{ getCurrencyText(withdrawDetail.currency) }}</span>
             </div>
           </el-col>
+        </el-row>
+        <el-row :gutter="20" class="sa-m-t-12">
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">收益余额:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.earningsBalance') }}:</span>
               <span class="value">৳{{ withdrawDetail.earningsBalance || 0 }}</span>
             </div>
           </el-col>
-        </el-row>
-        <el-row :gutter="20" class="sa-m-t-12">
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">提款金额:</span>
+            <div class="info-item" :style="{ '--label-width': formLabelWidth }">
+              <span class="label">{{ t('modules.withdraw.amount') }}:</span>
               <span class="value">৳{{ withdrawDetail.amount || 0 }}</span>
             </div>
           </el-col>
           <el-col :span="6">
-            <div class="info-item">
-              <span class="label">提款手续费:</span>
-              <span class="value">৳{{ withdrawDetail.fee || 0 }}</span>
-            </div>
+            <!-- 预留位置 -->
+          </el-col>
+          <el-col :span="6">
+            <!-- 预留位置 -->
           </el-col>
         </el-row>
       </div>
 
       <!-- 操作日志 -->
       <div class="operation-logs sa-m-b-26 mt-40px">
-        <h3 class="sa-m-b-20">操作日志</h3>
+        <h3 class="sa-m-b-20">{{ t('modules.withdraw.operationLog') }}</h3>
         <div class="sa-table-wrap">
-          <el-table
-            class="sa-table"
-            :data="operationLogs.data"
-            v-loading="operationLogs.loading"
-            stripe
-            border
-          >
+          <el-table class="sa-table" :data="operationLogs.data" v-loading="operationLogs.loading" stripe border>
             <template #empty>
               <sa-empty />
             </template>
-            <el-table-column prop="createTime" label="时间" min-width="160" align="center">
+            <el-table-column prop="createTime" :label="t('modules.withdraw.operationTime')" min-width="160"
+              align="center">
               <template #default="scope">
                 {{ scope.row.createTime || '--' }}
               </template>
             </el-table-column>
-            <el-table-column prop="createUser" label="操作人" min-width="120" align="center">
+            <el-table-column prop="createUser" :label="t('modules.withdraw.operator')" min-width="120" align="center">
               <template #default="scope">
                 {{ scope.row.createUser || '--' }}
               </template>
             </el-table-column>
-            <el-table-column prop="type" label="事项" min-width="200">
+            <el-table-column prop="type" :label="t('modules.withdraw.operationType')" min-width="200">
               <template #default="scope">
                 {{ getLogTypeText(scope.row.type) }}
               </template>
             </el-table-column>
-            <el-table-column prop="remark" label="备注" min-width="300">
+            <el-table-column prop="remark" :label="t('modules.withdraw.remark')" min-width="300">
               <template #default="scope">
                 {{ scope.row.remark || '--' }}
               </template>
@@ -155,177 +155,228 @@
 </template>
 
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import { api, financeUtils } from '../finance.service';
-  import WithdrawAudit from './audit.vue';
-
-  const props = defineProps({
-    modal: {
-      type: Object,
-      default: () => ({}),
-    },
-  });
-
-  const withdrawDetail = ref({});
-  const loading = ref(false);
-
-  // 操作日志分页
-  const { pageData: logPageData } = usePagination();
-  const operationLogs = reactive({
-    data: [],
-    loading: false,
-    total: 0,
-    pageData: logPageData,
-  });
-
-  // 使用公共配置的函数
-  const getStatusType = (status) => financeUtils.getStatusType(status, 'withdraw');
-  const getStatusText = financeUtils.getWithdrawStatusText;
-  const getAccountTypeText = financeUtils.getAccountTypeText;
-  const getChannelType = financeUtils.getChannelType;
-  const getChannelText = financeUtils.getChannelText;
-  const getCurrencyText = financeUtils.getCurrencyText;
-  const getLogTypeText = financeUtils.getLogTypeText;
-
-  // 获取提款详情
-  async function getWithdrawDetail() {
-    if (!props.modal?.params?.id) return;
-
-    loading.value = true;
-    try {
-      const { code, data } = await api.withdraw.withdrawDetail(props.modal.params.id);
-      if (code == '200') {
-        withdrawDetail.value = data;
-        // 获取详情后更新操作日志
-        getOperationLogs();
-      }
-    } catch (error) {
-      console.error('获取提款详情失败:', error);
-    } finally {
-      loading.value = false;
-    }
-  }
+import { onMounted, reactive, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useModal, usePagination } from '@/sheep/hooks';
+import { useFormConfig } from '@/hooks/useFormConfig';
+import { api, financeUtils } from '../finance.service';
+import WithdrawAudit from './audit.vue';
+
+const { t } = useI18n();
+
+// 使用表单配置hooks来处理不同语言的宽度
+const { formLabelWidth } = useFormConfig({ enWidth: '150px' });
 
-  // 获取操作日志 - 从详情数据中获取
-  function getOperationLogs() {
-    operationLogs.loading = true;
-    try {
-      // 使用详情数据中的 bizLogs
-      operationLogs.data = withdrawDetail.value.bizLogs || [];
-      operationLogs.total = operationLogs.data.length;
-    } catch (error) {
-      console.error('获取操作日志失败:', error);
-    } finally {
-      operationLogs.loading = false;
+const props = defineProps({
+  modal: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+
+const withdrawDetail = ref({});
+const loading = ref(false);
+
+// 操作日志分页
+const { pageData: logPageData } = usePagination();
+const operationLogs = reactive({
+  data: [],
+  loading: false,
+  total: 0,
+  pageData: logPageData,
+});
+
+// 使用公共配置的函数
+const getStatusType = (status) => financeUtils.getStatusType(status, 'withdraw');
+const getStatusText = financeUtils.getWithdrawStatusText;
+const getAccountTypeText = financeUtils.getAccountTypeText;
+const getChannelType = financeUtils.getChannelType;
+const getChannelText = financeUtils.getChannelText;
+const getCurrencyText = financeUtils.getCurrencyText;
+const getLogTypeText = financeUtils.getLogTypeText;
+
+// 本地账户类型文本函数(解决国际化问题)
+const getAccountTypeTextLocal = (accountType) => {
+  const typeMap = {
+    1: () => t('modules.finance.walletBalance'),
+    2: () => t('modules.finance.accountEarnings'),
+  };
+  const typeFn = typeMap[accountType] || typeMap[parseInt(accountType)];
+  return typeFn ? typeFn() : t('common.unknown');
+};
+
+// 提款方式文本函数(临时定义,等后端确认后更新)
+const getMethodText = (method) => {
+  const methodMap = {
+    1: t('modules.withdraw.bankTransfer'),
+    2: t('modules.withdraw.onlinePayment'),
+    3: t('modules.withdraw.mobilePayment'),
+    4: t('modules.withdraw.walletPayment'),
+  };
+  return methodMap[method] || t('common.unknown');
+};
+
+// 获取提款详情
+async function getWithdrawDetail() {
+  if (!props.modal?.params?.id) return;
+
+  loading.value = true;
+  try {
+    const { code, data } = await api.withdraw.withdrawDetail(props.modal.params.id);
+    if (code == '200') {
+      withdrawDetail.value = data;
+      // 获取详情后更新操作日志
+      getOperationLogs();
     }
+  } catch (error) {
+    console.error('获取提款详情失败:', error);
+  } finally {
+    loading.value = false;
   }
+}
 
-  // 审核通过
-  function handleApprove() {
-    useModal(
-      WithdrawAudit,
-      {
-        width: '500px',
-        title: '提款审核通过',
-        id: withdrawDetail.value.id,
-        type: 'approve',
-      },
-      {
-        confirm: () => {
-          getWithdrawDetail();
-        },
-      },
-    );
+// 获取操作日志 - 从详情数据中获取
+function getOperationLogs() {
+  operationLogs.loading = true;
+  try {
+    // 使用详情数据中的 bizLogs
+    operationLogs.data = withdrawDetail.value.bizLogs || [];
+    operationLogs.total = operationLogs.data.length;
+  } catch (error) {
+    console.error('获取操作日志失败:', error);
+  } finally {
+    operationLogs.loading = false;
   }
+}
 
-  // 审核拒绝
-  function handleReject() {
-    useModal(
-      WithdrawAudit,
-      {
-        width: '500px',
-        title: '提款审核拒绝',
-        id: withdrawDetail.value.id,
-        type: 'reject',
+// 审核通过
+function handleApprove() {
+  useModal(
+    WithdrawAudit,
+    {
+      width: '500px',
+      title: '提款审核通过',
+      id: withdrawDetail.value.id,
+      type: 'approve',
+    },
+    {
+      confirm: () => {
+        getWithdrawDetail();
       },
-      {
-        confirm: () => {
-          getWithdrawDetail();
-        },
+    },
+  );
+}
+
+// 审核拒绝
+function handleReject() {
+  useModal(
+    WithdrawAudit,
+    {
+      width: '500px',
+      title: '提款审核拒绝',
+      id: withdrawDetail.value.id,
+      type: 'reject',
+    },
+    {
+      confirm: () => {
+        getWithdrawDetail();
       },
-    );
-  }
+    },
+  );
+}
 
-  onMounted(() => {
-    getWithdrawDetail();
-  });
+onMounted(() => {
+  getWithdrawDetail();
+});
 </script>
 
 <style lang="scss" scoped>
-  .withdraw-detail {
-    .operation-buttons {
-      .el-button {
-        margin-left: 8px;
-      }
+.withdraw-detail {
+  .operation-buttons {
+    .el-button {
+      margin-left: 8px;
     }
+  }
 
-    .basic-info {
-      h3 {
-        font-size: 16px;
-        font-weight: 600;
-        color: var(--sa-title);
-      }
+  .basic-info {
+    h3 {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--sa-title);
+    }
 
-      .info-item {
-        display: flex;
-        align-items: center;
-        margin-bottom: 8px;
+    .info-item {
+      display: flex;
+      align-items: center;
+      margin-bottom: 8px;
 
-        .label {
-          flex-shrink: 0;
-          color: var(--sa-subfont);
-          font-size: 14px;
-          min-width: 80px;
-        }
+      .label {
+        flex-shrink: 0;
+        color: var(--sa-subfont);
+        font-size: 14px;
+        min-width: var(--label-width, 80px);
+      }
 
-        .value {
-          color: var(--sa-subtitle);
-          font-size: 14px;
-          font-weight: 500;
-        }
+      .value {
+        color: var(--sa-subtitle);
+        font-size: 14px;
+        font-weight: 500;
       }
     }
+  }
 
-    .operation-logs {
-      h3 {
-        font-size: 16px;
-        font-weight: 600;
-        color: var(--sa-title);
-      }
+  .operation-logs {
+    h3 {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--sa-title);
+    }
 
-      .sa-table {
-        border-radius: 8px;
-        overflow: hidden;
-        border: 1px solid #dcdfe6;
+    .sa-table {
+      border-radius: 8px;
+      overflow: hidden;
+      border: 1px solid #dcdfe6;
 
-        :deep(.el-table) {
-          border: none;
-          border-radius: 0;
-        }
+      :deep(.el-table) {
+        border: none;
+        border-radius: 0;
+      }
+
+      :deep(.el-table__header-wrapper) {
+        background: var(--sa-table-header-bg, #f5f7fa);
 
-        :deep(.el-table__header-wrapper) {
+        .el-table__header {
           background: var(--sa-table-header-bg, #f5f7fa);
 
-          .el-table__header {
-            background: var(--sa-table-header-bg, #f5f7fa);
+          th {
+            background: var(--sa-table-header-bg, #f5f7fa) !important;
+            font-weight: 600;
+            color: var(--sa-title, #303133);
+            border-bottom: 1px solid #dcdfe6;
+            border-right: 1px solid #dcdfe6;
 
-            th {
-              background: var(--sa-table-header-bg, #f5f7fa) !important;
-              font-weight: 600;
-              color: var(--sa-title, #303133);
+            &:first-child {
+              border-left: none;
+            }
+
+            &:last-child {
+              border-right: none;
+            }
+          }
+        }
+      }
+
+      :deep(.el-table__body-wrapper) {
+        .el-table__body {
+          tr {
+            &:hover {
+              background: #f5f7fa !important;
+            }
+
+            td {
               border-bottom: 1px solid #dcdfe6;
               border-right: 1px solid #dcdfe6;
+              border-left: none;
 
               &:first-child {
                 border-left: none;
@@ -335,37 +386,14 @@
                 border-right: none;
               }
             }
-          }
-        }
 
-        :deep(.el-table__body-wrapper) {
-          .el-table__body {
-            tr {
-              &:hover {
-                background: #f5f7fa !important;
-              }
-
-              td {
-                border-bottom: 1px solid #dcdfe6;
-                border-right: 1px solid #dcdfe6;
-                border-left: none;
-
-                &:first-child {
-                  border-left: none;
-                }
-
-                &:last-child {
-                  border-right: none;
-                }
-              }
-
-              &:last-child td {
-                border-bottom: none;
-              }
+            &:last-child td {
+              border-bottom: none;
             }
           }
         }
       }
     }
   }
+}
 </style>

+ 297 - 283
src/app/shop/admin/finance/withdraw/index.vue

@@ -3,127 +3,117 @@
     <el-header class="sa-header">
       <!-- 简化搜索组件 -->
       <div class="search-container">
-        <sa-search-simple
-          key="withdraw-search"
-          :searchFields="searchFields"
-          :defaultValues="defaultSearchValues"
-          @search="handleSearch"
-          @reset="handleReset"
-        />
+        <sa-search-simple key="withdraw-search" :searchFields="searchFields" :defaultValues="defaultSearchValues"
+          @search="handleSearch" @reset="handleReset" />
       </div>
       <!-- 状态Tab切换 -->
       <el-tabs class="sa-tabs" v-model="currentStatus" @tab-change="handleTabChange">
-        <el-tab-pane label="全部" name="all"></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="审核拒绝" name="3"></el-tab-pane>
-        <el-tab-pane label="提现成功" name="4"></el-tab-pane>
-        <el-tab-pane label="提现失败" name="5"></el-tab-pane>
-        <el-tab-pane label="超时取消" name="6"></el-tab-pane>
+        <el-tab-pane :label="t('common.all')" name="all"></el-tab-pane>
+        <el-tab-pane :label="t('modules.withdraw.processing')" name="1"></el-tab-pane>
+        <el-tab-pane :label="t('modules.withdraw.approved')" name="2"></el-tab-pane>
+        <el-tab-pane :label="t('modules.withdraw.rejected')" name="3"></el-tab-pane>
+        <el-tab-pane :label="t('modules.withdraw.success')" name="4"></el-tab-pane>
+        <el-tab-pane :label="t('modules.withdraw.failed')" name="5"></el-tab-pane>
+        <el-tab-pane :label="t('modules.withdraw.timeout')" name="6"></el-tab-pane>
       </el-tabs>
       <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">提款管理</div>
+        <div class="label sa-flex">{{ t('modules.withdraw.title') }}</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="exportRecords"
-          >
-            {{ exportLoading ? '导出中...' : '导出记录' }}
+          <el-button icon="Download" type="primary" :loading="exportLoading" :disabled="exportLoading"
+            @click="exportRecords">
+            {{ exportLoading ? t('modules.withdraw.exporting') : t('modules.withdraw.exportRecords') }}
           </el-button>
         </div>
       </div>
     </el-header>
     <el-main class="sa-p-0">
       <div class="sa-table-wrap panel-block panel-block--bottom" v-loading="loading">
-        <el-table
-          height="100%"
-          class="sa-table"
-          :data="table.data"
-          @sort-change="fieldFilter"
-          row-key="id"
-          stripe
-        >
+        <el-table height="100%" class="sa-table" :data="table.data" @sort-change="fieldFilter" row-key="id" stripe>
           <template #empty>
             <sa-empty />
           </template>
-          <el-table-column prop="orderNo" label="提款订单号" min-width="180" sortable="custom">
+          <el-table-column prop="orderNo" :label="t('modules.withdraw.orderNo')" min-width="180" sortable="custom">
             <template #default="scope">
               <span class="sa-table-line-1">{{ scope.row.orderNo || '-' }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="用户名" min-width="120">
+          <el-table-column :label="t('modules.withdraw.userName')" min-width="120">
             <template #default="scope">
               <el-link type="primary" :underline="true" @click="viewUserDetail(scope.row)">
                 {{ scope.row.userName || '-' }}
               </el-link>
             </template>
           </el-table-column>
-          <el-table-column prop="userPhone" label="手机号" min-width="130" align="center">
+          <el-table-column prop="userPhone" :label="t('modules.withdraw.phoneNo')" min-width="130" align="center">
             <template #default="scope">
               {{ scope.row.userPhone || '-' }}
             </template>
           </el-table-column>
-          <el-table-column label="提款类型" min-width="100" align="center">
+          <el-table-column :label="t('modules.withdraw.accountType')" min-width="100" align="center">
             <template #default="scope">
               <el-tag :type="scope.row.accountType === 1 ? 'primary' : 'success'">
                 {{ getAccountTypeText(scope.row.accountType) }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="提款渠道" min-width="100" align="center">
+          <el-table-column :label="t('modules.withdraw.channel')" min-width="130" align="center">
             <template #default="scope">
               <el-tag :type="getChannelType(scope.row.channel)">
                 {{ getChannelText(scope.row.channel) }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="币种" min-width="80" align="center">
+          <el-table-column :label="t('modules.withdraw.method')" min-width="130" align="center">
+            <template #default="scope">
+              <el-tag :type="getMethodType(scope.row.method)">
+                {{ getMethodText(scope.row.method) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('modules.withdraw.currency')" min-width="80" align="center">
             <template #default="scope">
               {{ getCurrencyText(scope.row.currency) }}
             </template>
           </el-table-column>
-          <el-table-column label="提款金额" min-width="120" align="center">
+          <el-table-column :label="t('modules.withdraw.amount')" min-width="130" align="center">
             <template #default="scope"> ৳{{ scope.row.amount || 0 }} </template>
           </el-table-column>
-          <el-table-column label="状态" min-width="100" align="center">
+          <el-table-column :label="t('common.status')" min-width="100" align="center">
             <template #default="scope">
               <el-tag :type="getStatusType(scope.row.status)">
                 {{ getStatusText(scope.row.status) }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="收款银行" min-width="150" align="center">
+          <el-table-column :label="t('modules.withdraw.receivingBank')" min-width="150" align="center">
             <template #default="scope">
               {{ scope.row.bank || '-' }}
             </template>
           </el-table-column>
-          <el-table-column label="收款账户名称" min-width="130" align="center">
+          <el-table-column :label="t('modules.withdraw.receivingAccountName')" min-width="130" align="center">
             <template #default="scope">
               {{ scope.row.bankAccountName || '-' }}
             </template>
           </el-table-column>
-          <el-table-column label="收款账户" min-width="160" align="center">
+          <el-table-column :label="t('modules.withdraw.receivingAccount')" min-width="160" align="center">
             <template #default="scope">
               {{ scope.row.bankAccount || '-' }}
             </template>
           </el-table-column>
-          <el-table-column prop="transTime" label="下单时间" min-width="160" sortable="custom">
+          <el-table-column prop="transTime" :label="t('modules.withdraw.createTime')" min-width="160" sortable="custom">
             <template #default="scope">
               {{ scope.row.transTime || '-' }}
             </template>
           </el-table-column>
-          <el-table-column label="成功时间" min-width="160">
+          <el-table-column :label="t('modules.withdraw.successTime')" min-width="160">
             <template #default="scope">
               {{ scope.row.successTime || '--' }}
             </template>
           </el-table-column>
-          <el-table-column fixed="right" label="操作" min-width="80">
+          <el-table-column fixed="right" :label="t('common.actions')" min-width="80">
             <template #default="scope">
-              <el-button link type="primary" @click="viewDetail(scope.row)">详情</el-button>
+              <el-button link type="primary" @click="viewDetail(scope.row)">{{ t('common.detail') }}</el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -137,266 +127,290 @@
   </el-container>
 </template>
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { ElMessage } from 'element-plus';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import { api, financeUtils } from '../finance.service';
-  import UserDetail from '../../user/list/detail.vue';
-  import WithdrawDetail from './detail.vue';
-
-  const { pageData } = usePagination();
-
-  // 当前状态标签
-  const currentStatus = ref('all');
-
-  // 当前搜索条件
-  const currentSearchParams = ref({});
-
-  // 导出loading状态
-  const exportLoading = ref(false);
-
-  // 搜索字段配置
-  const searchFields = reactive({
-    userName: {
-      type: 'input',
-      label: '用户名',
-      placeholder: '请输入用户名',
-      width: 150,
-    },
-    userPhone: {
-      type: 'input',
-      label: '手机号',
-      placeholder: '请输入手机号',
-      width: 150,
-    },
-    orderNo: {
-      type: 'input',
-      label: '订单号',
-      placeholder: '请输入订单号',
-      width: 180,
-    },
-    channel: {
-      type: 'select',
-      label: '提现通道',
-      placeholder: '请选择通道',
-      width: 120,
-      options: [
-        { label: 'TKPAY', value: 1 },
-        { label: '3QPAY', value: 2 },
-      ],
-    },
-    timeType: {
-      type: 'select',
-      label: '时间类型',
-      placeholder: '请选择时间类型',
-      width: 120,
-      options: [
-        { label: '下单时间', value: 1 },
-        { label: '成功时间', value: 2 },
-      ],
-    },
-    date_range: {
-      type: 'daterange',
-      label: '时间范围',
-      placeholder: '请选择时间范围',
-      width: 400,
-    },
-  });
-
-  // 默认搜索值
-  const defaultSearchValues = reactive({
-    userName: '',
-    userPhone: '',
-    orderNo: '',
-    channel: '',
-    timeType: 1, // 默认下单时间
-    date_range: [],
-  });
-  // 列表
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
-  });
-  const loading = ref(true);
-
-  // 使用公共配置的函数
-  const getStatusType = (status) => financeUtils.getStatusType(status, 'withdraw');
-  const getStatusText = financeUtils.getWithdrawStatusText;
-  const getChannelType = financeUtils.getChannelType;
-  const getChannelText = financeUtils.getChannelText;
-  const getCurrencyText = financeUtils.getCurrencyText;
-  const getAccountTypeText = financeUtils.getAccountTypeText;
-
-  // 获取数据
-  async function getData(page, searchParams = {}) {
-    if (page) pageData.page = page;
-    loading.value = true;
-
-    try {
-      // 构建请求参数
-      let params = {
-        page: pageData.page,
-        size: pageData.size,
-        ...searchParams,
-      };
-
-      // 处理日期范围
-      if (searchParams.date_range && searchParams.date_range.length === 2) {
-        params.startTime = searchParams.date_range[0];
-        params.endTime = searchParams.date_range[1];
-        delete params.date_range;
-      }
-
-      // 调用真实API
-      const { code, data } = await api.withdraw.list(params, false);
-
-      if (code == '200') {
-        table.data = data.list || [];
-        pageData.page = data.pageNum || 1;
-        pageData.size = data.pageSize || 10;
-        pageData.total = data.total || 0;
-      }
-    } catch (error) {
-      console.error('获取数据失败:', error);
-      ElMessage.error('获取数据失败');
-    } finally {
-      loading.value = false;
-    }
-  }
-  // 标签切换处理
-  const handleTabChange = (status) => {
-    currentStatus.value = status;
+import { onMounted, reactive, ref, computed } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import { useModal, usePagination } from '@/sheep/hooks';
+import { api, financeUtils } from '../finance.service';
+import UserDetail from '../../user/list/detail.vue';
+import WithdrawDetail from './detail.vue';
 
-    // 根据不同状态设置不同的筛选参数
-    let statusParams = {};
+const { t } = useI18n();
+const { pageData } = usePagination();
 
-    if (status !== 'all') {
-      statusParams.status = parseInt(status);
-    }
+// 当前状态标签
+const currentStatus = ref('all');
 
-    // 整合搜索条件和状态筛选
-    const allParams = { ...currentSearchParams.value, ...statusParams };
-    getData(1, allParams);
-  };
+// 当前搜索条件
+const currentSearchParams = ref({});
 
-  // 搜索处理
-  const handleSearch = (searchParams) => {
-    currentSearchParams.value = searchParams;
+// 导出loading状态
+const exportLoading = ref(false);
 
-    // 获取当前tab的状态参数
-    let statusParams = {};
-    if (currentStatus.value !== 'all') {
-      statusParams.status = parseInt(currentStatus.value);
-    }
+// 搜索字段配置 - 使用计算属性以支持语言切换
+const searchFields = computed(() => ({
+  userName: {
+    type: 'input',
+    label: t('modules.withdraw.userName'),
+    placeholder: t('modules.withdraw.enterUserName'),
+    width: 150,
+  },
+  userPhone: {
+    type: 'input',
+    label: t('modules.withdraw.phoneNo'),
+    placeholder: t('modules.withdraw.enterPhoneNo'),
+    width: 150,
+  },
+  orderNo: {
+    type: 'input',
+    label: t('modules.withdraw.orderNo'),
+    placeholder: t('modules.withdraw.enterOrderNo'),
+    width: 180,
+  },
+  channel: {
+    type: 'select',
+    label: t('modules.withdraw.channel'),
+    placeholder: t('modules.withdraw.selectChannel'),
+    width: 120,
+    options: [
+      { label: 'TKPAY', value: 1 },
+      { label: '3QPAY', value: 2 },
+    ],
+  },
+  timeType: {
+    type: 'select',
+    label: t('modules.withdraw.timeType'),
+    placeholder: t('modules.withdraw.selectTimeType'),
+    width: 120,
+    options: [
+      { label: t('modules.withdraw.createTime'), value: 1 },
+      { label: t('modules.withdraw.successTime'), value: 2 },
+    ],
+  },
+  date_range: {
+    type: 'daterange',
+    label: t('modules.withdraw.timeRange'),
+    placeholder: t('modules.withdraw.selectTimeRange'),
+    width: 400,
+  },
+}));
+
+// 默认搜索值
+const defaultSearchValues = reactive({
+  userName: '',
+  userPhone: '',
+  orderNo: '',
+  channel: '',
+  timeType: 1, // 默认下单时间
+  date_range: [],
+});
+// 列表
+const table = reactive({
+  data: [],
+  order: '',
+  sort: '',
+  selected: [],
+});
+const loading = ref(true);
+
+// 使用公共配置的函数
+const getStatusType = (status) => financeUtils.getStatusType(status, 'withdraw');
+const getStatusText = financeUtils.getWithdrawStatusText;
+const getChannelType = financeUtils.getChannelType;
+const getChannelText = financeUtils.getChannelText;
+const getCurrencyText = financeUtils.getCurrencyText;
+const getAccountTypeText = financeUtils.getAccountTypeText;
 
-    // 整合搜索条件和状态筛选
-    const allParams = { ...searchParams, ...statusParams };
-    getData(1, allParams);
+// 提款方式相关函数(临时定义,等后端确认后更新)
+const getMethodType = (method) => {
+  const typeMap = {
+    1: 'primary',
+    2: 'success',
+    3: 'warning',
+    4: 'info',
   };
+  return typeMap[method] || '';
+};
 
-  // 重置处理
-  const handleReset = () => {
-    currentSearchParams.value = {};
+const getMethodText = (method) => {
+  const methodMap = {
+    1: t('modules.withdraw.bankTransfer'),
+    2: t('modules.withdraw.onlinePayment'),
+    3: t('modules.withdraw.mobilePayment'),
+    4: t('modules.withdraw.walletPayment'),
+  };
+  return methodMap[method] || t('common.unknown');
+};
 
-    // 获取当前tab的状态参数
-    let statusParams = {};
-    if (currentStatus.value !== 'all') {
-      statusParams.status = parseInt(currentStatus.value);
+// 获取数据
+async function getData(page, searchParams = {}) {
+  if (page) pageData.page = page;
+  loading.value = true;
+
+  try {
+    // 构建请求参数
+    let params = {
+      page: pageData.page,
+      size: pageData.size,
+      ...searchParams,
+    };
+
+    // 处理日期范围
+    if (searchParams.date_range && searchParams.date_range.length === 2) {
+      params.startTime = searchParams.date_range[0];
+      params.endTime = searchParams.date_range[1];
+      delete params.date_range;
     }
 
-    getData(1, statusParams);
-  };
+    // 调用真实API
+    const { code, data } = await api.withdraw.list(params, false);
 
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
+    if (code == '200') {
+      table.data = data.list || [];
+      pageData.page = data.pageNum || 1;
+      pageData.size = data.pageSize || 10;
+      pageData.total = data.total || 0;
+    }
+  } catch (error) {
+    console.error('获取数据失败:', error);
+    ElMessage.error(t('common.fetchDataFailed'));
+  } finally {
+    loading.value = false;
   }
-  // 查看详情
-  function viewDetail(row) {
-    useModal(
-      WithdrawDetail,
-      {
-        title: '提款详情',
-        type: 'view',
-        id: row.id,
-        size: '80%',
-      },
-      {
-        confirm: () => {
-          // 审核操作后刷新列表
-          getData();
-        },
-        close: () => {
-          getData();
-        },
-      },
-    );
+}
+// 标签切换处理
+const handleTabChange = (status) => {
+  currentStatus.value = status;
+
+  // 根据不同状态设置不同的筛选参数
+  let statusParams = {};
+
+  if (status !== 'all') {
+    statusParams.status = parseInt(status);
   }
 
-  // 查看用户详情
-  function viewUserDetail(row) {
-    useModal(
-      UserDetail,
-      {
-        title: '用户详情',
-        type: 'view',
-        id: row.user_id,
+  // 整合搜索条件和状态筛选
+  const allParams = { ...currentSearchParams.value, ...statusParams };
+  getData(1, allParams);
+};
+
+// 搜索处理
+const handleSearch = (searchParams) => {
+  currentSearchParams.value = searchParams;
+
+  // 获取当前tab的状态参数
+  let statusParams = {};
+  if (currentStatus.value !== 'all') {
+    statusParams.status = parseInt(currentStatus.value);
+  }
+
+  // 整合搜索条件和状态筛选
+  const allParams = { ...searchParams, ...statusParams };
+  getData(1, allParams);
+};
+
+// 重置处理
+const handleReset = () => {
+  currentSearchParams.value = {};
+
+  // 获取当前tab的状态参数
+  let statusParams = {};
+  if (currentStatus.value !== 'all') {
+    statusParams.status = parseInt(currentStatus.value);
+  }
+
+  getData(1, statusParams);
+};
+
+// table 字段排序
+function fieldFilter({ prop, order }) {
+  table.order = order == 'ascending' ? 'asc' : 'desc';
+  table.sort = prop;
+  getData();
+}
+// 查看详情
+function viewDetail(row) {
+  useModal(
+    WithdrawDetail,
+    {
+      title: t('modules.withdraw.detail'),
+      type: 'view',
+      id: row.id,
+      size: '80%',
+    },
+    {
+      confirm: () => {
+        // 审核操作后刷新列表
+        getData();
       },
-      {
-        confirm: () => {
-          // 用户详情页面通常不需要确认回调
-        },
+      close: () => {
+        getData();
       },
-    );
-  }
+    },
+  );
+}
 
-  // 导出记录
-  async function exportRecords() {
-    if (exportLoading.value) return; // 防止重复点击
-
-    exportLoading.value = true;
-    try {
-      // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
-      const exportParams = {
-        ...currentSearchParams.value, // 当前搜索参数
-      };
-
-      // 添加当前状态筛选
-      if (currentStatus.value !== 'all') {
-        exportParams.status = parseInt(currentStatus.value);
-      }
-
-      // 处理时间范围搜索(与 getData 函数保持一致)
-      if (exportParams.date_range && exportParams.date_range.length === 2) {
-        exportParams.startTime = exportParams.date_range[0];
-        exportParams.endTime = exportParams.date_range[1];
-        delete exportParams.date_range;
-      }
-
-      // 调用导出API,所有下载逻辑都在REPORT函数中处理
-      await api.withdraw.report(exportParams, '提现记录');
-    } finally {
-      exportLoading.value = false;
+// 查看用户详情
+function viewUserDetail(row) {
+  useModal(
+    UserDetail,
+    {
+      title: t('modules.withdraw.userDetail'),
+      type: 'view',
+      id: row.user_id,
+    },
+    {
+      confirm: () => {
+        // 用户详情页面通常不需要确认回调
+      },
+    },
+  );
+}
+
+// 导出记录
+async function exportRecords() {
+  if (exportLoading.value) return; // 防止重复点击
+
+  exportLoading.value = true;
+  try {
+    // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
+    const exportParams = {
+      ...currentSearchParams.value, // 当前搜索参数
+    };
+
+    // 添加当前状态筛选
+    if (currentStatus.value !== 'all') {
+      exportParams.status = parseInt(currentStatus.value);
     }
+
+    // 处理时间范围搜索(与 getData 函数保持一致)
+    if (exportParams.date_range && exportParams.date_range.length === 2) {
+      exportParams.startTime = exportParams.date_range[0];
+      exportParams.endTime = exportParams.date_range[1];
+      delete exportParams.date_range;
+    }
+
+    // 调用导出API,所有下载逻辑都在REPORT函数中处理
+    await api.withdraw.report(exportParams, t('modules.withdraw.records'));
+  } finally {
+    exportLoading.value = false;
   }
+}
 
-  onMounted(() => {
-    getData();
-  });
+onMounted(() => {
+  getData();
+});
 </script>
 <style lang="scss" scoped>
-  .withdraw-view {
-    .el-header {
-      height: auto;
-    }
-    .el-main {
-      .sa-table-wrap {
-        height: 100%;
-      }
+.withdraw-view {
+  .el-header {
+    height: auto;
+  }
+
+  .el-main {
+    .sa-table-wrap {
+      height: 100%;
     }
   }
+}
 </style>

+ 582 - 662
src/app/shop/admin/goods/goods/index.vue

@@ -5,12 +5,8 @@
         <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"
+              v-model="currentSearchParams" @search="handleSearch" @reset="handleReset">
               <template #custom="{ data }">
                 <el-form-item :label="t('form.priceRange')">
                   <div class="range-input-group">
@@ -23,18 +19,9 @@
             </sa-search-simple>
           </div>
           <el-tabs class="sa-tabs" v-model="currentStatus" @tab-click="handleTabClick">
-            <el-tab-pane
-              :label="`${t('common.all')}(${statusCounts.all})`"
-              name="all"
-            ></el-tab-pane>
-            <el-tab-pane
-              :label="`${t('modules.goods.onSale')}(${statusCounts.alreadyListed})`"
-              name="up"
-            ></el-tab-pane>
-            <el-tab-pane
-              :label="`${t('modules.goods.offSale')}(${statusCounts.removed})`"
-              name="down"
-            ></el-tab-pane>
+            <el-tab-pane :label="`${t('common.all')}(${statusCounts.all})`" name="all"></el-tab-pane>
+            <el-tab-pane :label="`${t('modules.goods.onSale')}(${statusCounts.alreadyListed})`" name="up"></el-tab-pane>
+            <el-tab-pane :label="`${t('modules.goods.offSale')}(${statusCounts.removed})`" name="down"></el-tab-pane>
             <!-- <el-tab-pane :label="`已删除(${statusCounts.recycle})`" name="isRecycle"></el-tab-pane> -->
           </el-tabs>
           <div class="sa-title sa-flex sa-row-between">
@@ -42,11 +29,7 @@
               <span class="left">{{ t('modules.goods.goodsList') }}</span>
             </div>
             <div>
-              <el-button
-                class="sa-button-refresh"
-                icon="RefreshRight"
-                @click="getData()"
-              ></el-button>
+              <el-button class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
               <el-button icon="Plus" type="primary" @click="addRow">{{
                 t('common.create')
               }}</el-button>
@@ -56,46 +39,26 @@
 
         <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" width="48" align="center"></el-table-column>
-              <el-table-column
-                sortable="custom"
-                prop="id"
-                :label="t('modules.goods.goodsId')"
-                min-width="100"
-              ></el-table-column>
+              <el-table-column sortable="custom" prop="id" :label="t('modules.goods.goodsId')"
+                min-width="100"></el-table-column>
               <el-table-column :label="t('modules.goods.goodsInfo')" min-width="300">
                 <template #default="scope">
                   <div class="sa-flex">
-                    <el-image
-                      :src="scope.row.image"
-                      style="width: 60px; height: 60px; margin-right: 12px"
-                      fit="cover"
-                    />
+                    <el-image :src="scope.row.image" style="width: 60px; height: 60px; margin-right: 12px"
+                      fit="cover" />
                     <div>
                       <div class="goods-title">{{ scope.row.storeName }}</div>
                     </div>
                   </div>
                 </template>
               </el-table-column>
-              <el-table-column
-                sortable="custom"
-                prop="price"
-                :label="t('form.price')"
-                min-width="150"
-                align="center"
-              >
+              <el-table-column sortable="custom" prop="price" :label="t('form.price')" min-width="150" align="center">
                 <template #default="scope">
                   <div>{{ t('modules.goods.marketPrice') }}: ৳{{ scope.row.otPrice }}</div>
                   <div>{{ t('modules.goods.salePrice') }}: ৳{{ scope.row.price }}</div>
@@ -112,30 +75,12 @@
                   </el-tag>
                 </template>
               </el-table-column>
-              <el-table-column
-                prop="itemNumber"
-                :label="t('modules.goods.goodsNumber')"
-                min-width="100"
-                align="center"
-              ></el-table-column>
-              <el-table-column
-                prop="stock"
-                :label="t('form.stock')"
-                min-width="100"
-                align="center"
-              ></el-table-column>
-              <el-table-column
-                sortable="custom"
-                prop="sales"
-                :label="t('modules.goods.sales')"
-                min-width="100"
-                align="center"
-              ></el-table-column>
-              <el-table-column
-                :label="t('modules.goods.goodsSupplier')"
-                min-width="120"
-                align="center"
-              >
+              <el-table-column prop="itemNumber" :label="t('modules.goods.goodsNumber')" min-width="100"
+                align="center"></el-table-column>
+              <el-table-column prop="stock" :label="t('form.stock')" min-width="100" align="center"></el-table-column>
+              <el-table-column sortable="custom" prop="sales" :label="t('modules.goods.sales')" min-width="100"
+                align="center"></el-table-column>
+              <el-table-column :label="t('modules.goods.goodsSupplier')" min-width="120" align="center">
                 <template #default="scope">
                   <div>{{ scope.row.itemSupplier || t('modules.goods.defaultSupplier') }}</div>
                 </template>
@@ -146,13 +91,9 @@
                     <el-button class="is-link" type="primary" @click="editRow(scope.row)">
                       {{ t('common.edit') }}
                     </el-button>
-                    <el-popconfirm
-                      width="fit-content"
-                      :confirm-button-text="t('common.confirm')"
-                      :cancel-button-text="t('common.cancel')"
-                      :title="t('message.confirmDelete')"
-                      @confirm="deleteRow(scope.row.id)"
-                    >
+                    <el-popconfirm width="fit-content" :confirm-button-text="t('common.confirm')"
+                      :cancel-button-text="t('common.cancel')" :title="t('message.confirmDelete')"
+                      @confirm="deleteRow(scope.row.id)">
                       <template #reference>
                         <el-button class="is-link sa-m-l-12" type="danger">
                           {{ t('common.delete') }}
@@ -170,11 +111,8 @@
     <el-footer class="goods-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="handlePageChange" />
@@ -185,651 +123,633 @@
 </template>
 
 <script setup>
-  import { onMounted, reactive, ref, computed } from 'vue';
-  import { api } from '../goods.service';
-  import { ElMessageBox, ElMessage } from 'element-plus';
-  import { useModal, usePagination } from '@/sheep/hooks';
-  import { useI18n } from 'vue-i18n';
-
-  import GoodsEdit from './edit.vue';
-  import TabEdit from './tab-edit.vue';
-
-  // 多语言支持
-  const { t } = useI18n();
-
-  // getType
-  const statusList = reactive({
-    data: [],
-    color: {
-      1: 'success',
-      0: 'danger',
-      all: '',
-      up: 'success',
-      down: 'danger',
-      hidden: 'info',
-    },
-  });
-  async function getType() {
-    const { code, data } = await api.category.list({ size: 100 });
-    // 设置分类数据
-    if (code === '200') {
-      searchFields.cateId.options = data.list.map((cat) => ({
-        label: cat.name,
-        value: cat.id,
-      }));
-    }
-    statusList.data = data.status;
+import { onMounted, reactive, ref, computed } from 'vue';
+import { api } from '../goods.service';
+import { ElMessageBox, ElMessage } from 'element-plus';
+import { useModal, usePagination } from '@/sheep/hooks';
+import { useI18n } from 'vue-i18n';
+
+import GoodsEdit from './edit.vue';
+import TabEdit from './tab-edit.vue';
+
+// 多语言支持
+const { t } = useI18n();
+
+// getType
+const statusList = reactive({
+  data: [],
+  color: {
+    1: 'success',
+    0: 'danger',
+    all: '',
+    up: 'success',
+    down: 'danger',
+    hidden: 'info',
+  },
+});
+async function getType() {
+  const { code, data } = await api.category.list({ size: 100 });
+  // 设置分类数据
+  if (code === '200') {
+    searchFields.cateId.options = data.list.map((cat) => ({
+      label: cat.name,
+      value: cat.id,
+    }));
   }
-
-  // 搜索字段配置
-  const searchFields = reactive({
-    storeName: {
-      type: 'input',
-      get label() {
-        return t('modules.goods.goodsName');
-      },
-      get placeholder() {
-        return t('form.inputName');
-      },
-      width: 200,
+  statusList.data = data.status;
+}
+
+// 搜索字段配置
+const searchFields = reactive({
+  storeName: {
+    type: 'input',
+    get label() {
+      return t('modules.goods.goodsName');
     },
-    cateId: {
-      type: 'select',
-      get label() {
-        return t('modules.goods.goodsCategory');
-      },
-      get placeholder() {
-        return t('form.selectCategory');
-      },
-      options: [],
-      width: 200,
+    get placeholder() {
+      return t('form.inputName');
     },
-  });
-
-  // 默认搜索值
-  const defaultSearchValues = reactive({
-    storeName: '',
-    cateId: '',
-    status: '',
-    supplier: '',
-    create_time: [],
-    minPrice: '',
-    maxPrice: '',
-  });
-
-  // 搜索可见性控制
-
-  // 当前状态标签
-  const currentStatus = ref('all');
-
-  // 当前搜索条件
-  const currentSearchParams = ref({});
-
-  // 状态数量
-  const statusCounts = reactive({
-    all: 0,
-    alreadyListed: 0,
-    removed: 0,
-    recycle: 0,
-  });
-
-  // 标签点击处理(只在用户主动点击时触发)
-  const handleTabClick = (tab) => {
-    const status = tab.props.name;
-    currentStatus.value = status;
-
-    // 根据不同状态设置不同的筛选参数
-    let statusParams = {};
-
-    switch (status) {
-      case 'all':
-        // 全部:不传递任何状态参数
-        break;
-      case 'up':
-        // 已上架:isShow = 1
-        statusParams.isShow = 1;
-        break;
-      case 'down':
-        // 已下架:isShow = 0
-        statusParams.isShow = 0;
-        break;
-      case 'isRecycle':
-        statusParams.isRecycle = 1;
-        break;
-    }
-
-    // 整合搜索条件和状态筛选
-    const allParams = { ...currentSearchParams.value, ...statusParams };
-    getData(1, allParams);
-  };
-
-  // 搜索处理
-  const handleSearch = (searchParams) => {
-    currentSearchParams.value = searchParams;
-
-    // 获取当前tab的状态参数
-    let statusParams = {};
-    switch (currentStatus.value) {
-      case 'up':
-        statusParams.isShow = 1;
-        break;
-      case 'down':
-        statusParams.isShow = 0;
-        break;
-      case 'isRecycle':
-        statusParams.isRecycle = 1;
-        break;
-    }
-
-    // 整合搜索条件和状态筛选
-    const allParams = { ...searchParams, ...statusParams };
-    getData(1, allParams);
-  };
-
-  // 重置处理
-  const handleReset = () => {
-    currentSearchParams.value = {};
-
-    // 获取当前tab的状态参数
-    let statusParams = {};
-    switch (currentStatus.value) {
-      case 'up':
-        statusParams.isShow = 1;
-        break;
-      case 'down':
-        statusParams.isShow = 0;
-        break;
-      case 'isRecycle':
-        statusParams.isRecycle = 1;
-        break;
-    }
-
-    getData(1, statusParams);
-  };
-
-  // 获取状态数量
-  const getStatusCounts = async () => {
-    try {
-      const response = await api.goods.getStatusNum();
-      if (response.code == '200') {
-        Object.assign(statusCounts, response.data);
-      }
-    } catch (error) {
-      console.error('获取状态数量失败:', error);
-    }
-  };
-
-  const loading = ref(true);
-
-  // 表格
-  const table = reactive({
-    data: [],
-    order: 'desc',
-    sort: 'id',
-    selected: [],
-  });
-
-  const { pageData } = usePagination();
-
-  // 获取数据
-  async function getData(page, searchParams = {}, refreshStatusCounts = true) {
-    if (page) pageData.page = page;
-    loading.value = true;
-    const { code, data } = await api.goods.list({
-      page: pageData.page,
-      size: pageData.size,
-      ...searchParams,
-    });
-    if (code == '200') {
-      table.data = data.list;
-      pageData.page = data.pageNum;
-      pageData.size = data.pageSize;
-      pageData.total = data.total;
-    }
-    loading.value = false;
-
-    // 可选择性刷新状态数量
-    if (refreshStatusCounts) {
-      getStatusCounts();
-    }
+    width: 200,
+  },
+  cateId: {
+    type: 'select',
+    get label() {
+      return t('modules.goods.goodsCategory');
+    },
+    get placeholder() {
+      return t('form.selectCategory');
+    },
+    options: [],
+    width: 200,
+  },
+});
+
+// 默认搜索值
+const defaultSearchValues = reactive({
+  storeName: '',
+  cateId: '',
+  status: '',
+  supplier: '',
+  create_time: [],
+  minPrice: '',
+  maxPrice: '',
+});
+
+// 搜索可见性控制
+
+// 当前状态标签
+const currentStatus = ref('all');
+
+// 当前搜索条件
+const currentSearchParams = ref({});
+
+// 状态数量
+const statusCounts = reactive({
+  all: 0,
+  alreadyListed: 0,
+  removed: 0,
+  recycle: 0,
+});
+
+// 标签点击处理(只在用户主动点击时触发)
+const handleTabClick = (tab) => {
+  const status = tab.props.name;
+  currentStatus.value = status;
+  // 直接调用 getData,会自动使用当前的搜索条件和状态
+  getData(1);
+};
+
+// 搜索处理
+const handleSearch = (searchParams) => {
+  // 由于使用了 v-model,currentSearchParams 会自动更新
+  // 直接调用 getData,会自动使用当前的搜索条件和状态
+  getData(1);
+};
+
+// 重置处理
+const handleReset = () => {
+  // 由于使用了 v-model,currentSearchParams 会自动清空
+  // 直接调用 getData,会自动使用当前的搜索条件和状态
+  getData(1);
+};
+
+// 获取状态数量
+const getStatusCounts = async () => {
+  try {
+    const response = await api.goods.getStatusNum();
+    if (response.code == '200') {
+      Object.assign(statusCounts, response.data);
+    }
+  } catch (error) {
+    console.error('获取状态数量失败:', error);
+  }
+};
+
+const loading = ref(true);
+
+// 表格
+const table = reactive({
+  data: [],
+  order: 'desc',
+  sort: 'id',
+  selected: [],
+});
+
+const { pageData } = usePagination();
+
+// 获取数据
+async function getData(page, searchParams = null, refreshStatusCounts = true) {
+  if (page) pageData.page = page;
+  loading.value = true;
+
+  // 构建请求参数 - 优先使用传入的参数,否则使用双向绑定的搜索条件
+  const finalSearchParams = searchParams !== null ? searchParams : currentSearchParams.value;
+
+  // 根据当前状态添加筛选条件
+  let statusParams = {};
+  switch (currentStatus.value) {
+    case 'up':
+      statusParams.isShow = 1;
+      break;
+    case 'down':
+      statusParams.isShow = 0;
+      break;
+    case 'isRecycle':
+      statusParams.isRecycle = 1;
+      break;
   }
 
-  // 分页处理
-  function handlePageChange(page) {
-    getData(page, currentSearchParams.value, false); // 分页时不需要刷新状态数量
+  const { code, data } = await api.goods.list({
+    page: pageData.page,
+    size: pageData.size,
+    ...finalSearchParams,
+    ...statusParams,
+  });
+  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(null, {}, false); // 排序时不需要刷新状态数量
+  // 可选择性刷新状态数量
+  if (refreshStatusCounts) {
+    getStatusCounts();
   }
+}
+
+// 分页处理
+function handlePageChange(page) {
+  getData(page, null, false); // 分页时不需要刷新状态数量,使用双向绑定的搜索条件
+}
+
+// table 字段排序
+function fieldFilter({ prop, order }) {
+  table.order = order == 'ascending' ? 'asc' : 'desc';
+  table.sort = prop;
+  getData(null, {}, false); // 排序时不需要刷新状态数量
+}
+
+// table 批量选择
+function changeSelection(row) {
+  table.selected = row;
+}
+
+// 批量操作
+const batchHandleTools = computed(() => [
+  {
+    type: 'up',
+    label: t('modules.goods.batchOnSale'),
+    auth: 'shop.admin.goods.goods.edit',
+    class: 'success',
+  },
+  {
+    type: 'down',
+    label: t('modules.goods.batchOffSale'),
+    auth: 'shop.admin.goods.goods.edit',
+    class: 'danger',
+  },
+  {
+    type: 'delete',
+    label: t('modules.goods.batchDelete'),
+    auth: 'shop.admin.goods.goods.delete',
+    class: 'danger',
+  },
+]);
+async function batchHandle(type) {
+  let ids = [];
+  table.selected.forEach((row) => {
+    ids.push(row.id);
+  });
 
-  // table 批量选择
-  function changeSelection(row) {
-    table.selected = row;
+  switch (type) {
+    case 'delete':
+      ElMessageBox.confirm(t('message.confirmBatchDelete'), t('message.tip'), {
+        confirmButtonText: t('common.confirm'),
+        cancelButtonText: t('common.cancel'),
+        type: 'warning',
+      }).then(() => {
+        deleteRow(ids.join(','));
+      });
+      break;
+    case 'up':
+      ElMessageBox.confirm(t('message.confirmBatchOnSale'), t('message.tip'), {
+        confirmButtonText: t('common.confirm'),
+        cancelButtonText: t('common.cancel'),
+        type: 'warning',
+      }).then(() => {
+        batchShowStatus(ids.join(','), 1);
+      });
+      break;
+    case 'down':
+      ElMessageBox.confirm(t('message.confirmBatchOffSale'), t('message.tip'), {
+        confirmButtonText: t('common.confirm'),
+        cancelButtonText: t('common.cancel'),
+        type: 'warning',
+      }).then(() => {
+        batchShowStatus(ids.join(','), 0);
+      });
+      break;
+    default:
+      handleCommand({ id: ids.join(','), type: type });
   }
+}
 
-  // 批量操作
-  const batchHandleTools = computed(() => [
+function addRow() {
+  useModal(
+    GoodsEdit,
     {
-      type: 'up',
-      label: t('modules.goods.batchOnSale'),
-      auth: 'shop.admin.goods.goods.edit',
-      class: 'success',
+      title: '添加商品',
+      type: 'add',
+      width: '1200px',
     },
     {
-      type: 'down',
-      label: t('modules.goods.batchOffSale'),
-      auth: 'shop.admin.goods.goods.edit',
-      class: 'danger',
+      confirm: () => {
+        getData();
+      },
     },
+  );
+}
+
+// 使用Tab编辑器新增商品
+function addRowWithTab() {
+  useModal(
+    TabEdit,
     {
-      type: 'delete',
-      label: t('modules.goods.batchDelete'),
-      auth: 'shop.admin.goods.goods.delete',
-      class: 'danger',
+      title: '新增商品(Tab版)',
+      type: 'add',
+      width: '80%',
+      height: '80%',
     },
-  ]);
-  async function batchHandle(type) {
-    let ids = [];
-    table.selected.forEach((row) => {
-      ids.push(row.id);
-    });
-
-    switch (type) {
-      case 'delete':
-        ElMessageBox.confirm(t('message.confirmBatchDelete'), t('message.tip'), {
-          confirmButtonText: t('common.confirm'),
-          cancelButtonText: t('common.cancel'),
-          type: 'warning',
-        }).then(() => {
-          deleteRow(ids.join(','));
-        });
-        break;
-      case 'up':
-        ElMessageBox.confirm(t('message.confirmBatchOnSale'), t('message.tip'), {
-          confirmButtonText: t('common.confirm'),
-          cancelButtonText: t('common.cancel'),
-          type: 'warning',
-        }).then(() => {
-          batchShowStatus(ids.join(','), 1);
-        });
-        break;
-      case 'down':
-        ElMessageBox.confirm(t('message.confirmBatchOffSale'), t('message.tip'), {
-          confirmButtonText: t('common.confirm'),
-          cancelButtonText: t('common.cancel'),
-          type: 'warning',
-        }).then(() => {
-          batchShowStatus(ids.join(','), 0);
-        });
-        break;
-      default:
-        handleCommand({ id: ids.join(','), type: type });
-    }
-  }
-
-  function addRow() {
-    useModal(
-      GoodsEdit,
-      {
-        title: '添加商品',
-        type: 'add',
-        width: '1200px',
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-
-  // 使用Tab编辑器新增商品
-  function addRowWithTab() {
-    useModal(
-      TabEdit,
-      {
-        title: '新增商品(Tab版)',
-        type: 'add',
-        width: '80%',
-        height: '80%',
-      },
-      {
-        confirm: () => {
-          getData();
-        },
-        success: () => {
-          getData();
-        },
+    {
+      confirm: () => {
+        getData();
       },
-    );
-  }
-
-  // 使用Tab编辑器编辑商品
-  function editRowWithTab(row) {
-    useModal(
-      TabEdit,
-      {
-        title: '编辑商品(Tab版)',
-        type: 'edit',
-        id: row.id,
-        width: '80%',
-        height: '80%',
+      success: () => {
+        getData();
       },
-      {
-        confirm: () => {
-          getData();
-        },
-        success: () => {
-          getData();
-        },
+    },
+  );
+}
+
+// 使用Tab编辑器编辑商品
+function editRowWithTab(row) {
+  useModal(
+    TabEdit,
+    {
+      title: '编辑商品(Tab版)',
+      type: 'edit',
+      id: row.id,
+      width: '80%',
+      height: '80%',
+    },
+    {
+      confirm: () => {
+        getData();
       },
-    );
-  }
-  function editRow(row) {
-    useModal(
-      GoodsEdit,
-      {
-        title: '编辑',
-        type: 'edit',
-        id: row.id,
-        width: '1200px',
+      success: () => {
+        getData();
       },
-      {
-        confirm: () => {
-          getData();
-        },
+    },
+  );
+}
+function editRow(row) {
+  useModal(
+    GoodsEdit,
+    {
+      title: '编辑',
+      type: 'edit',
+      id: row.id,
+      width: '1200px',
+    },
+    {
+      confirm: () => {
+        getData();
       },
-    );
-  }
+    },
+  );
+}
+
+async function deleteRow(ids) {
+  // 支持单个ID或多个ID(逗号分隔)
+  const idsParam = Array.isArray(ids) ? ids.join(',') : ids;
+  await api.goods.delete({ ids: idsParam });
+  getData();
+}
+
+// 批量上下架
+async function batchShowStatus(ids, showStatus) {
+  try {
+    const response = await api.goods.batchShowStatus({
+      ids: ids,
+      showStatus: showStatus,
+    });
 
-  async function deleteRow(ids) {
-    // 支持单个ID或多个ID(逗号分隔)
-    const idsParam = Array.isArray(ids) ? ids.join(',') : ids;
-    await api.goods.delete({ ids: idsParam });
-    getData();
+    if (response.code == '200') {
+      ElMessage.success(showStatus === 1 ? '批量上架成功' : '批量下架成功');
+      getData(); // 刷新列表
+    } else {
+      ElMessage.error(response.message || '操作失败');
+    }
+  } catch (error) {
+    ElMessage.error('操作失败:' + error.message);
   }
+}
 
-  // 批量上下架
-  async function batchShowStatus(ids, showStatus) {
-    try {
-      const response = await api.goods.batchShowStatus({
-        ids: ids,
-        showStatus: showStatus,
-      });
+async function handleCommand(e) {
+  await api.goods.edit(e.id, {
+    status: e.type,
+  });
+  getData();
+}
 
-      if (response.code == '200') {
-        ElMessage.success(showStatus === 1 ? '批量上架成功' : '批量下架成功');
-        getData(); // 刷新列表
-      } else {
-        ElMessage.error(response.message || '操作失败');
-      }
-    } catch (error) {
-      ElMessage.error('操作失败:' + error.message);
-    }
+onMounted(() => {
+  getType();
+  getData(); // getData 内部会调用 getStatusCounts(),避免重复调用
+});
+</script>
+<style lang="scss">
+.goods-dropdown {
+  .status-up {
+    color: var(--el-color-success);
   }
 
-  async function handleCommand(e) {
-    await api.goods.edit(e.id, {
-      status: e.type,
-    });
-    getData();
+  .status-down {
+    color: var(--el-color-danger);
   }
 
-  onMounted(() => {
-    getType();
-    getData(); // getData 内部会调用 getStatusCounts(),避免重复调用
-  });
-</script>
-<style lang="scss">
-  .goods-dropdown {
-    .status-up {
-      color: var(--el-color-success);
-    }
-    .status-down {
-      color: var(--el-color-danger);
-    }
-    .status-hidden {
-      color: var(--el-color-info);
-    }
+  .status-hidden {
+    color: var(--el-color-info);
   }
+}
 </style>
 <style lang="scss" scoped>
-  .goods-view {
-    .el-main {
-      .sa-table-wrap {
-        overflow: hidden;
-        height: 100%;
-        :deep() {
-          .el-table__empty-text {
-            margin-left: 0;
-          }
-        }
-        .title {
-          height: 20px;
-          line-height: 20px;
-          font-size: 14px;
-          color: var(--sa-font);
+.goods-view {
+  .el-main {
+    .sa-table-wrap {
+      overflow: hidden;
+      height: 100%;
+
+      :deep() {
+        .el-table__empty-text {
+          margin-left: 0;
         }
-        .subtitle {
-          height: 16px;
-          line-height: 16px;
-          font-size: 12px;
-          color: var(--sa-subfont);
+      }
+
+      .title {
+        height: 20px;
+        line-height: 20px;
+        font-size: 14px;
+        color: var(--sa-font);
+      }
+
+      .subtitle {
+        height: 16px;
+        line-height: 16px;
+        font-size: 12px;
+        color: var(--sa-subfont);
+      }
+
+      .goods-tag {
+        max-width: 76px;
+        padding: 3px 8px;
+        border-radius: 10px;
+        line-height: 14px;
+        font-size: 12px;
+        font-weight: 400;
+        margin-right: 8px;
+        cursor: pointer;
+
+        &:last-of-type {
+          margin-right: 0;
         }
-        .goods-tag {
-          max-width: 76px;
-          padding: 3px 8px;
-          border-radius: 10px;
-          line-height: 14px;
-          font-size: 12px;
-          font-weight: 400;
-          margin-right: 8px;
-          cursor: pointer;
-          &:last-of-type {
-            margin-right: 0;
-          }
-          &.promos-goods {
-            color: #faad14;
-            background: rgba(250, 173, 20, 0.16);
-          }
-          &.groupon_ladder-goods,
-          &.groupon-goods {
-            color: var(--el-color-primary);
-            background: var(--t-bg-active);
-          }
-          &.seckill-goods {
-            color: #ff4d4f;
-            background: rgba(255, 77, 79, 0.16);
-          }
+
+        &.promos-goods {
+          color: #faad14;
+          background: rgba(250, 173, 20, 0.16);
         }
-        .sku {
-          width: fit-content;
-          height: 20px;
-          line-height: 1;
-          display: inline-flex;
-          align-items: center;
-          padding: 0 8px;
-          font-size: 12px;
-          color: #fff;
-          background: var(--el-color-primary);
-          border-radius: 10px;
-          cursor: pointer;
+
+        &.groupon_ladder-goods,
+        &.groupon-goods {
+          color: var(--el-color-primary);
+          background: var(--t-bg-active);
         }
-        .stock {
-          cursor: pointer;
-          .add-stock {
-            margin-left: 8px;
-            color: var(--el-color-primary);
-          }
+
+        &.seckill-goods {
+          color: #ff4d4f;
+          background: rgba(255, 77, 79, 0.16);
         }
-        .el-tag {
-          padding: 0 8px;
-          border: none;
-          cursor: pointer;
-          :deep() {
-            .el-tag__content {
-              display: flex;
-              align-items: center;
-            }
-          }
+      }
+
+      .sku {
+        width: fit-content;
+        height: 20px;
+        line-height: 1;
+        display: inline-flex;
+        align-items: center;
+        padding: 0 8px;
+        font-size: 12px;
+        color: #fff;
+        background: var(--el-color-primary);
+        border-radius: 10px;
+        cursor: pointer;
+      }
+
+      .stock {
+        cursor: pointer;
+
+        .add-stock {
+          margin-left: 8px;
+          color: var(--el-color-primary);
         }
       }
-      .sa-expand-table {
+
+      .el-tag {
+        padding: 0 8px;
+        border: none;
+        cursor: pointer;
+
         :deep() {
-          .el-table__header-wrapper {
-            display: none;
+          .el-tag__content {
+            display: flex;
+            align-items: center;
           }
         }
-        .sku-text {
-          font-size: 12px;
-          color: var(--sa-font);
-        }
       }
     }
-    .expand-arrow {
-      color: #fff !important;
-    }
 
-    .goods-index-main {
-      overflow: hidden;
-    }
+    .sa-expand-table {
+      :deep() {
+        .el-table__header-wrapper {
+          display: none;
+        }
+      }
 
-    .el-aside {
-      --el-aside-width: 161px;
-      border-right: 1px solid var(--sa-border);
-      padding: 0 3px;
+      .sku-text {
+        font-size: 12px;
+        color: var(--sa-font);
+      }
     }
+  }
 
-    .el-aside .category-all {
-      line-height: 48px;
-      border-radius: 4px;
-      padding: 0 12px;
-      position: relative;
-      cursor: pointer;
-      font-size: 14px;
-      color: var(--sa-title);
-    }
+  .expand-arrow {
+    color: #fff !important;
+  }
 
-    .el-aside .category-all span:last-child {
-      color: var(--el-color-primary);
-    }
+  .goods-index-main {
+    overflow: hidden;
+  }
 
-    .el-aside .category-all:hover {
-      background: var(--t-bg-hover);
-    }
+  .el-aside {
+    --el-aside-width: 161px;
+    border-right: 1px solid var(--sa-border);
+    padding: 0 3px;
+  }
 
-    .el-aside .category-all.is-active {
-      background: var(--t-bg-active);
-      color: var(--el-color-primary);
-    }
+  .el-aside .category-all {
+    line-height: 48px;
+    border-radius: 4px;
+    padding: 0 12px;
+    position: relative;
+    cursor: pointer;
+    font-size: 14px;
+    color: var(--sa-title);
+  }
 
-    .el-aside .category-all::after {
-      content: '';
-      position: absolute;
-      right: 9px;
-      bottom: 0;
-      width: 138px;
-      height: 1px;
-      background: var(--sa-space);
-    }
+  .el-aside .category-all span:last-child {
+    color: var(--el-color-primary);
+  }
 
-    .el-aside .el-tree {
-      --el-tree-node-content-height: 40px;
-      --el-tree-node-hover-bg-color: var(--t-bg-hover);
-    }
+  .el-aside .category-all:hover {
+    background: var(--t-bg-hover);
+  }
 
-    .el-aside .el-tree > .el-tree-node > .el-tree-node__content {
-      --el-tree-node-content-height: 54px;
-      align-items: flex-start;
-      position: relative;
-    }
+  .el-aside .category-all.is-active {
+    background: var(--t-bg-active);
+    color: var(--el-color-primary);
+  }
 
-    .el-aside .el-tree > .el-tree-node > .el-tree-node__content::after {
-      content: '';
-      position: absolute;
-      right: 9px;
-      bottom: 0;
-      width: 138px;
-      height: 1px;
-      background: var(--sa-space);
-    }
+  .el-aside .category-all::after {
+    content: '';
+    position: absolute;
+    right: 9px;
+    bottom: 0;
+    width: 138px;
+    height: 1px;
+    background: var(--sa-space);
+  }
 
-    .el-aside .el-tree .el-tree-node__content {
-      /* align-items: flex-start; */
-      padding-top: 6px;
-      border-radius: 4px;
-    }
+  .el-aside .el-tree {
+    --el-tree-node-content-height: 40px;
+    --el-tree-node-hover-bg-color: var(--t-bg-hover);
+  }
 
-    .el-aside .el-tree .name {
-      line-height: 20px;
-      font-size: 12px;
-      color: var(--sa-font);
-    }
+  .el-aside .el-tree>.el-tree-node>.el-tree-node__content {
+    --el-tree-node-content-height: 54px;
+    align-items: flex-start;
+    position: relative;
+  }
 
-    .el-aside .el-tree .goods {
-      line-height: 16px;
-      font-size: 12px;
-      color: var(--sa-subfont);
-    }
+  .el-aside .el-tree>.el-tree-node>.el-tree-node__content::after {
+    content: '';
+    position: absolute;
+    right: 9px;
+    bottom: 0;
+    width: 138px;
+    height: 1px;
+    background: var(--sa-space);
+  }
 
-    .el-aside .el-tree:not(.all) .el-tree-node.is-current > .el-tree-node__content {
-      background: var(--t-bg-active);
-    }
+  .el-aside .el-tree .el-tree-node__content {
+    /* align-items: flex-start; */
+    padding-top: 6px;
+    border-radius: 4px;
+  }
 
-    .el-aside
-      .el-tree:not(.all)
-      .el-tree-node.is-current
-      > .el-tree-node__content
-      .el-tree-node__expand-icon {
-      color: var(--el-color-primary);
-    }
+  .el-aside .el-tree .name {
+    line-height: 20px;
+    font-size: 12px;
+    color: var(--sa-font);
+  }
 
-    .el-aside .el-tree:not(.all) .el-tree-node.is-current > .el-tree-node__content .name {
-      color: var(--el-color-primary);
-    }
+  .el-aside .el-tree .goods {
+    line-height: 16px;
+    font-size: 12px;
+    color: var(--sa-subfont);
+  }
 
-    .el-aside .el-tree:not(.all) .el-tree-node.is-current > .el-tree-node__content .goods {
-      color: var(--el-color-primary);
-    }
-    .goods-index-footer {
-      --el-footer-height: fit-content;
-    }
+  .el-aside .el-tree:not(.all) .el-tree-node.is-current>.el-tree-node__content {
+    background: var(--t-bg-active);
+  }
 
-    .goods-title {
-      font-size: 14px;
-      font-weight: 500;
-      color: var(--sa-font);
-      margin-bottom: 4px;
-      line-height: 1.4;
-    }
+  .el-aside .el-tree:not(.all) .el-tree-node.is-current>.el-tree-node__content .el-tree-node__expand-icon {
+    color: var(--el-color-primary);
+  }
 
-    .goods-supplier {
-      font-size: 12px;
-      color: var(--sa-subfont);
-      line-height: 1.2;
-    }
+  .el-aside .el-tree:not(.all) .el-tree-node.is-current>.el-tree-node__content .name {
+    color: var(--el-color-primary);
+  }
 
-    .search-container {
-      .range-input-group {
-        display: flex;
-        align-items: center;
-        gap: 8px;
-        width: 100%;
+  .el-aside .el-tree:not(.all) .el-tree-node.is-current>.el-tree-node__content .goods {
+    color: var(--el-color-primary);
+  }
 
-        .el-input {
-          flex: 1;
-        }
+  .goods-index-footer {
+    --el-footer-height: fit-content;
+  }
 
-        .range-separator {
-          color: var(--el-text-color-regular);
-          font-size: 14px;
-          white-space: nowrap;
-        }
+  .goods-title {
+    font-size: 14px;
+    font-weight: 500;
+    color: var(--sa-font);
+    margin-bottom: 4px;
+    line-height: 1.4;
+  }
+
+  .goods-supplier {
+    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%;
+
+      .el-input {
+        flex: 1;
+      }
+
+      .range-separator {
+        color: var(--el-text-color-regular);
+        font-size: 14px;
+        white-space: nowrap;
       }
     }
   }
+}
 </style>

+ 16 - 29
src/app/shop/admin/marketing/group/index.vue

@@ -5,8 +5,8 @@
         <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"
+              v-model="currentSearchParams" @search="handleSearch" @reset="handleReset">
             </sa-search-simple>
           </div>
           <el-tabs class="sa-tabs" v-model="currentStatus" @tab-change="handleTabChange">
@@ -187,7 +187,7 @@ const defaultSearchValues = reactive({
   id: '',
 });
 
-// 当前搜索条件
+// 当前搜索条件 - 使用 ref 支持双向绑定
 const currentSearchParams = ref({});
 // 列表
 const table = reactive({
@@ -199,22 +199,20 @@ const table = reactive({
 const loading = ref(true);
 
 // 获取数据
-async function getData(page, searchParams = {}) {
+async function getData(page, searchParams = null) {
   if (page) pageData.page = page;
   loading.value = true;
 
   try {
-    // 构建请求参数
+    // 构建请求参数 - 优先使用传入的参数,否则使用双向绑定的搜索条件
+    const finalSearchParams = searchParams !== null ? searchParams : currentSearchParams.value;
     const params = {
       page: pageData.page,
       size: pageData.size,
-      ...searchParams,
+      activeState: parseInt(currentStatus.value) ?? null,
+      ...finalSearchParams,
     };
 
-    // 根据当前状态添加筛选条件
-    if (currentStatus.value) {
-      params.activeState = parseInt(currentStatus.value);
-    }
 
     // 使用 CRUD 的 list 方法
     const { code, data, message } = await api.group.list(params);
@@ -240,33 +238,22 @@ async function getData(page, searchParams = {}) {
 // Tab切换处理
 const handleTabChange = (tabName) => {
   currentStatus.value = tabName;
-
-  // 构建筛选参数
-  const statusParams = tabName ? { activeState: parseInt(tabName) } : {};
-  const allParams = { ...currentSearchParams.value, ...statusParams };
-
-  getData(1, allParams);
+  // 直接调用 getData,会自动使用当前的搜索条件和状态
+  getData(1);
 };
 
 // 搜索处理
 const handleSearch = (searchParams) => {
-  currentSearchParams.value = searchParams;
-
-  // 构建筛选参数
-  const statusParams = currentStatus.value ? { activeState: parseInt(currentStatus.value) } : {};
-  const allParams = { ...searchParams, ...statusParams };
-
-  getData(1, allParams);
+  // 由于使用了 v-model,currentSearchParams 会自动更新
+  // 直接调用 getData,会自动使用当前的搜索条件和状态
+  getData(1);
 };
 
 // 重置搜索
 const handleReset = () => {
-  currentSearchParams.value = {};
-
-  // 构建筛选参数
-  const statusParams = currentStatus.value ? { activeState: parseInt(currentStatus.value) } : {};
-
-  getData(1, statusParams);
+  // 由于使用了 v-model,currentSearchParams 会自动清空
+  // 直接调用 getData,会自动使用当前的搜索条件和状态
+  getData(1);
 };
 // table 字段排序
 function fieldFilter({ prop, order }) {

+ 1 - 1
src/app/shop/admin/order/order.service.js

@@ -11,7 +11,7 @@ export const ORDER_STATUS = {
     3: { text: '已支付', type: 'success' },
     4: { text: '失败已退款', type: 'info' },
     5: { text: '待发货', type: 'warning' },
-    6: { text: '未中奖关闭', type: 'info' },
+    6: { text: '关闭', type: 'info' },
     7: { text: '待收货', type: 'primary' },
     8: { text: '订单完成', type: 'success' },
   },

+ 22 - 30
src/app/shop/admin/order/order/index.vue

@@ -5,8 +5,8 @@
         <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"
+              v-model="currentSearchParams" @search="handleSearch" @reset="handleReset">
               <template #custom="{ data }">
                 <el-form-item label="下单时间">
                   <el-date-picker v-model="data.createTime" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss"
@@ -232,17 +232,16 @@ const statusMap = {
 
 // 搜索处理
 const handleSearch = (searchParams) => {
-  // 合并搜索条件和当前tab状态
-  const statusParams = statusMap[currentStatus.value] || {};
-  const mergedParams = { ...searchParams, ...statusParams };
-  getData(1, mergedParams);
+  // 由于使用了 v-model,currentSearchParams 会自动更新
+  // 直接调用 getData,会自动使用当前的搜索条件和状态
+  getData(1);
 };
 
 // 重置处理
 const handleReset = () => {
-  // 重置时只保留当前tab状态
-  const statusParams = statusMap[currentStatus.value] || {};
-  getData(1, statusParams);
+  // 由于使用了 v-model,currentSearchParams 会自动清空
+  // 直接调用 getData,会自动使用当前的搜索条件和状态
+  getData(1);
 };
 
 // 标签切换处理
@@ -258,14 +257,8 @@ const handleTabChange = (status) => {
     },
   });
 
-  // 获取当前tab对应的状态参数
-  const statusParams = statusMap[status] || {};
-
-  // 合并当前搜索条件和状态参数
-  const mergedParams = { ...currentSearchParams.value, ...statusParams };
-
-  // 调用数据获取,无论成功与否都不影响tab状态
-  getData(1, mergedParams);
+  // 直接调用 getData,会自动使用当前的搜索条件和状态
+  getData(1);
 };
 
 // 通用状态处理函数
@@ -301,30 +294,29 @@ const { pageData } = usePagination();
 const currentSearchParams = ref({});
 
 // 获取数据
-async function getData(page, searchParams = {}) {
-  // 分离搜索条件和状态参数
-  const { paid, status, refundStatus, ...pureSearchParams } = searchParams;
-
-  // 保存纯搜索条件(不包含状态参数)
-  if (Object.keys(pureSearchParams).length > 0) {
-    currentSearchParams.value = { ...pureSearchParams };
-  }
-
+async function getData(page, searchParams = null) {
   if (page) pageData.page = page;
   loading.value = true;
 
   try {
+    // 构建请求参数 - 优先使用传入的参数,否则使用双向绑定的搜索条件
+    const finalSearchParams = searchParams !== null ? searchParams : currentSearchParams.value;
+
+    // 根据当前状态添加筛选条件
+    const statusParams = statusMap[currentStatus.value] || {};
+    const allParams = { ...finalSearchParams, ...statusParams };
+
     // 构建请求参数
     const requestData = {
       page: pageData.page,
       size: pageData.size,
-      ...searchParams,
+      ...allParams,
     };
 
     // 处理时间范围搜索
-    if (searchParams.createTime && searchParams.createTime.length === 2) {
-      requestData.startTime = searchParams.createTime[0];
-      requestData.endTime = searchParams.createTime[1];
+    if (allParams.createTime && allParams.createTime.length === 2) {
+      requestData.startTime = allParams.createTime[0];
+      requestData.endTime = allParams.createTime[1];
       delete requestData.createTime;
     }
 

+ 17 - 16
src/app/shop/admin/user/list/index.vue

@@ -3,8 +3,8 @@
     <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"
+          v-model="currentSearchParams" @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')"
@@ -249,21 +249,24 @@ const table = reactive({
 const loading = ref(true);
 
 // 获取数据
-async function getData(page, searchParams = {}) {
+async function getData(page, searchParams = null) {
   if (page) pageData.page = page;
   loading.value = true;
 
-  // 构建请求参数
+  // 构建请求参数 - 优先使用传入的参数,否则使用双向绑定的搜索条件
+  const finalSearchParams = searchParams !== null ? searchParams : currentSearchParams.value;
+
   const requestData = {
     page: pageData.page,
     size: pageData.size,
     type: 0,
-    ...searchParams,
+    ...finalSearchParams,
   };
+
   // 处理时间范围搜索
-  if (searchParams.dateRange && searchParams.dateRange.length === 2) {
-    requestData.startTime = searchParams.dateRange[0];
-    requestData.endTime = searchParams.dateRange[1];
+  if (finalSearchParams.dateRange && finalSearchParams.dateRange.length === 2) {
+    requestData.startTime = finalSearchParams.dateRange[0];
+    requestData.endTime = finalSearchParams.dateRange[1];
     delete requestData.dateRange;
   }
 
@@ -335,19 +338,17 @@ const currentSearchParams = ref({});
 // 导出loading状态
 const exportLoading = ref(false);
 
-// 更新搜索参数处理函数
+// 搜索处理函数
 function handleSearch(searchParams) {
-  // 保存当前搜索参数
-  currentSearchParams.value = { ...searchParams };
-  // 执行搜索
-  getData(1, searchParams);
+  // 由于使用了 v-model,currentSearchParams 会自动更新
+  // 直接调用 getData,会自动使用当前的搜索条件
+  getData(1);
 }
 
 // 重置搜索参数处理函数
 function handleReset() {
-  // 清空当前搜索参数
-  currentSearchParams.value = {};
-  // 执行重置
+  // 由于使用了 v-model,currentSearchParams 会自动清空
+  // 直接调用 getData,会自动使用当前的搜索条件
   getData(1);
 }
 

+ 156 - 152
src/app/shop/admin/user/tag/index.vue

@@ -3,12 +3,8 @@
     <el-header class="sa-header">
       <!-- 简化搜索组件 -->
       <div class="search-container">
-        <sa-search-simple
-          :searchFields="searchFields"
-          :defaultValues="defaultSearchValues"
-          @search="(val) => getData(1, val)"
-          @reset="getData(1)"
-        >
+        <sa-search-simple :searchFields="searchFields" :defaultValues="defaultSearchValues"
+          v-model="currentSearchParams" @search="handleSearch" @reset="handleReset">
         </sa-search-simple>
       </div>
       <div class="sa-title sa-flex sa-row-between">
@@ -21,15 +17,8 @@
     </el-header>
     <el-main class="sa-p-0">
       <div class="sa-table-wrap panel-block panel-block--bottom" 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>
@@ -70,13 +59,8 @@
           <el-table-column fixed="right" label="操作" min-width="120">
             <template #default="scope">
               <el-button class="is-link" type="primary" @click="editRow(scope.row)">编辑</el-button>
-              <el-popconfirm
-                width="fit-content"
-                confirm-button-text="确认"
-                cancel-button-text="取消"
-                title="确认删除这条记录?"
-                @confirm="deleteApi(scope.row.id)"
-              >
+              <el-popconfirm width="fit-content" confirm-button-text="确认" cancel-button-text="取消" title="确认删除这条记录?"
+                @confirm="deleteApi(scope.row.id)">
                 <template #reference>
                   <el-button class="is-link" type="danger"> 删除 </el-button>
                 </template>
@@ -88,11 +72,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" />
@@ -101,141 +82,164 @@
   </el-container>
 </template>
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { api } from '../user.service';
-  import { ElMessageBox } from 'element-plus';
-  import { useModal } from '@/sheep/hooks';
-  import { usePagination } from '@/sheep/hooks';
-  import tagEdit from './edit.vue';
-  const { pageData } = usePagination();
+import { onMounted, reactive, ref } from 'vue';
+import { api } from '../user.service';
+import { ElMessageBox } from 'element-plus';
+import { useModal } from '@/sheep/hooks';
+import { usePagination } from '@/sheep/hooks';
+import tagEdit from './edit.vue';
+const { pageData } = usePagination();
 
-  // 搜索字段配置
-  const searchFields = reactive({
-    name: {
-      type: 'input',
-      label: '标签名称',
-      placeholder: '请输入标签名称',
-      width: 200,
-    },
-  });
-  // 默认搜索值
-  const defaultSearchValues = reactive({
-    name: '',
-  });
-  // 列表
-  const table = reactive({
-    data: [],
-    order: '',
-    sort: '',
-    selected: [],
+// 搜索字段配置
+const searchFields = reactive({
+  name: {
+    type: 'input',
+    label: '标签名称',
+    placeholder: '请输入标签名称',
+    width: 200,
+  },
+});
+// 默认搜索值
+const defaultSearchValues = reactive({
+  name: '',
+});
+
+// 当前搜索条件 - 使用 ref 支持双向绑定
+const currentSearchParams = ref({});
+
+// 列表
+const table = reactive({
+  data: [],
+  order: '',
+  sort: '',
+  selected: [],
+});
+const loading = ref(true);
+// 获取数据
+async function getData(page, searchParams = null) {
+  if (page) pageData.page = page;
+  loading.value = true;
+
+  // 构建请求参数 - 优先使用传入的参数,否则使用双向绑定的搜索条件
+  const finalSearchParams = searchParams !== null ? searchParams : currentSearchParams.value;
+
+  const { code, data } = await api.tag.list({
+    page: pageData.page,
+    size: pageData.size,
+    order: table.order,
+    ...finalSearchParams,
+    sort: table.sort,
   });
-  const loading = ref(true);
-  // 获取
-  async function getData(page, searchParams = {}) {
-    if (page) pageData.page = page;
-    loading.value = true;
-    const { code, data } = await api.tag.list({
-      page: pageData.page,
-      size: pageData.size,
-      order: table.order,
-      ...searchParams,
-      sort: table.sort,
-    });
-    console.log('API 响应:', error, data);
-    if (code == 200) {
-      table.data = data.data;
-      pageData.page = data.current_page;
-      pageData.size = data.per_page;
-      pageData.total = data.total;
-    }
-    loading.value = false;
+  console.log('API 响应:', error, data);
+  if (code == 200) {
+    table.data = data.data;
+    pageData.page = data.current_page;
+    pageData.size = data.per_page;
+    pageData.total = data.total;
   }
-  // table 字段排序
-  function fieldFilter({ prop, order }) {
-    table.order = order == 'ascending' ? 'asc' : 'desc';
-    table.sort = prop;
-    getData();
-  }
-  //table批量选择
-  function changeSelection(row) {
-    table.selected = row;
-  }
-  // 分页/批量操作
-  const batchHandleTools = [
+  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 = [
+  {
+    type: 'delete',
+    label: '删除',
+    auth: 'shop.admin.user.tag.delete',
+    class: 'danger',
+  },
+];
+function addRow() {
+  useModal(
+    tagEdit,
+    { title: '新建标签', type: 'add' },
     {
-      type: 'delete',
-      label: '删除',
-      auth: 'shop.admin.user.tag.delete',
-      class: 'danger',
-    },
-  ];
-  function addRow() {
-    useModal(
-      tagEdit,
-      { title: '新建标签', type: 'add' },
-      {
-        confirm: () => {
-          getData();
-        },
-      },
-    );
-  }
-  function editRow(row) {
-    useModal(
-      tagEdit,
-      {
-        title: '编辑标签',
-        type: 'edit',
-        id: row.id,
-      },
-      {
-        confirm: () => {
-          getData();
-        },
+      confirm: () => {
+        getData();
       },
-    );
-  }
-  // 删除api 单独批量可以直接调用
-  async function deleteApi(id) {
-    await api.tag.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;
-      default:
-        await api.tag.edit(ids.join(','), {
-          status: type,
-        });
+    },
+  );
+}
+function editRow(row) {
+  useModal(
+    tagEdit,
+    {
+      title: '编辑标签',
+      type: 'edit',
+      id: row.id,
+    },
+    {
+      confirm: () => {
         getData();
-    }
+      },
+    },
+  );
+}
+// 删除api 单独批量可以直接调用
+async function deleteApi(id) {
+  await api.tag.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;
+    default:
+      await api.tag.edit(ids.join(','), {
+        status: type,
+      });
+      getData();
   }
+}
 
-  onMounted(() => {
-    getData();
-  });
+// 搜索处理
+const handleSearch = (searchParams) => {
+  // 由于使用了 v-model,currentSearchParams 会自动更新
+  // 直接调用 getData,会自动使用当前的搜索条件
+  getData(1);
+};
+
+// 重置处理
+const handleReset = () => {
+  // 由于使用了 v-model,currentSearchParams 会自动清空
+  // 直接调用 getData,会自动使用当前的搜索条件
+  getData(1);
+};
+
+onMounted(() => {
+  getData();
+});
 </script>
 <style lang="scss" scoped>
-  .user-tag-view {
-    .el-header {
-      height: auto;
-    }
-    .el-main {
-      .sa-table-wrap {
-        height: 100%;
-      }
+.user-tag-view {
+  .el-header {
+    height: auto;
+  }
+
+  .el-main {
+    .sa-table-wrap {
+      height: 100%;
     }
   }
+}
 </style>

+ 420 - 14
src/locales/en-US/index.json

@@ -1,4 +1,5 @@
 {
+  "locale": "en-US",
   "common": {
     "save": "Save",
     "update": "Update",
@@ -89,7 +90,52 @@
     "english": "English",
     "unknown": "Unknown",
     "fetchDataFailed": "Failed to fetch data",
-    "createTime": "Create Time"
+    "createTime": "Create Time",
+    "title": "Title",
+    "identifier": "Identifier",
+    "enterUniqueIdentifier": "Please enter unique identifier",
+    "enterShortPath": "Please enter short path",
+    "weight": "Weight",
+    "enterWeight": "Please enter weight",
+    "normal": "Normal",
+    "disabled": "Disabled",
+    "includePermissions": "Include Permissions",
+    "name": "Name",
+    "operation": "Operation",
+    "enterName": "Please enter name",
+    "enterIdentifier": "Please enter identifier",
+    "addTemplate": "Add Template",
+    "selectIcon": "Select Icon",
+    "uploadCert": "Upload Certificate",
+    "noInfo": "No Info",
+    "cancelled": "Cancelled",
+    "loading": "Loading...",
+    "sort": "Sort",
+    "enterSort": "Please enter sort",
+    "actions": "Actions",
+    "add": "Add",
+    "save": "Save",
+    "update": "Update",
+    "edit": "Edit",
+    "delete": "Delete",
+    "confirm": "Confirm",
+    "cancel": "Cancel",
+    "createTime": "Create Time",
+    "to": "to",
+    "startDate": "Start Date",
+    "endDate": "End Date",
+    "status": "Status",
+    "all": "All",
+    "confirm": "Confirm",
+    "cancel": "Cancel",
+    "cancelled": "Cancelled",
+    "tip": "Tip",
+    "unknown": "Unknown",
+    "actions": "Actions",
+    "detail": "Detail",
+    "loading": "Loading...",
+    "fetchDataFailed": "Failed to fetch data",
+    "submit": "Submit"
   },
   "menu": {
     "dashboard": "Dashboard",
@@ -137,7 +183,23 @@
     "staffManagement": "Staff",
     "roleManagement": "Roles",
     "carouselBanner": "Carousel",
-    "paymentConfig": "Payment"
+    "paymentConfig": "Payment",
+    "menu": "Menu",
+    "page": "Page",
+    "action": "Action",
+    "adminManagement": "Admin Management",
+    "authSettings": "Auth Settings",
+    "notification": "Notification"
+  },
+  "taskbar": {
+    "close": "Close",
+    "closeOthers": "Close Others",
+    "profile": "Profile",
+    "changePassword": "Change Password",
+    "logout": "Logout",
+    "confirmLogout": "Are you sure you want to logout?",
+    "logoutSuccess": "Logout successful",
+    "superAdminCannotChangePassword": "Super admin cannot change password"
   },
   "form": {
     "name": "Name",
@@ -290,9 +352,14 @@
     "confirmBatchOffSale": "This operation will batch take goods off sale, continue?",
     "tip": "Tip",
     "selectionLimitReached": "Selection limit reached",
+    "fetchRoleListFailed": "Failed to fetch role list",
+    "fetchDetailFailed": "Failed to fetch details",
+    "submitFailed": "Submit failed",
+    "changePasswordFailed": "Failed to change password",
     "maxUploadLimit": "Maximum {count} images can be uploaded",
     "uploadError": "Upload failed",
     "unsupportedFormat": "{extension} format is not supported, please select {formats} format images",
+    "fetchDataFailed": "Failed to fetch data",
     "fileSizeExceeded": "Image size cannot exceed {size}MB",
     "saving": "Saving...",
     "deleting": "Deleting...",
@@ -377,7 +444,348 @@
       "roleRequired": "Please select role",
       "normal": "Normal",
       "disabled": "Disabled",
-      "locked": "Locked"
+      "locked": "Locked",
+      "application": "Application",
+      "selectAll": "Select All",
+      "addPermission": "+Add",
+      "initMenu": "Initialize Menu",
+      "noData": "No data",
+      "confirmDeleteRecord": "Are you sure you want to delete this record?",
+      "confirmInit": "This operation will clear all existing permissions and reinitialize menu permissions. Continue?\n⚠️ Note: This operation is irreversible!",
+      "initConfirmTitle": "Initialize Confirmation",
+      "initSuccess": "Menu permissions initialized successfully!",
+      "initFailed": "Initialization failed: {message}",
+      "createPermissionSuccess": "Permission created successfully",
+      "createPermissionFailed": "Failed to create permission",
+      "updateSortSuccess": "Sort order updated successfully",
+      "updateSortFailed": "Failed to update sort order",
+      "deletePermissionSuccess": "Permission deleted successfully",
+      "deletePermissionFailed": "Failed to delete permission",
+      "parentPermission": "Parent Permission",
+      "icon": "Icon",
+      "sort": "Sort",
+      "url": "URL",
+      "isAction": "Is Action Permission",
+      "assignPermissions": "Assign Permissions",
+      "permissionTree": "Permission Tree",
+      "normalTip": "This permission is normally available",
+      "disabledTip": "After disabling this permission, only super administrators can access it"
+    },
+    "banner": {
+      "title": "Banner Management",
+      "bannerTitle": "Banner Title",
+      "image": "Image",
+      "bannerImage": "Banner Image",
+      "linkType": "Link Type",
+      "linkAddress": "Link Address",
+      "internalLink": "Internal Link",
+      "externalLink": "External Link",
+      "addBanner": "Add Banner",
+      "editBanner": "Edit Banner",
+      "enterBannerTitle": "Please enter banner title",
+      "uploadImage": "Upload Image",
+      "enterLinkAddress": "Please enter link address",
+      "titleRequired": "Please enter banner title",
+      "imageRequired": "Please upload banner image",
+      "linkRequired": "Please enter link address"
+    },
+    "payment": {
+      "title": "Payment Config",
+      "collectionConfig": "Collection Config",
+      "payoutConfig": "Payout Config",
+      "paymentChannel": "Payment Channel",
+      "addChannel": "Add Channel",
+      "addPaymentChannel": "Add Payment Channel",
+      "editPaymentChannel": "Edit Payment Channel",
+      "collectionMethod": "Collection Method",
+      "collectionChannel": "Collection Channel",
+      "payoutMethod": "Payout Method",
+      "payoutChannel": "Payout Channel",
+      "channelName": "Channel Name",
+      "channelCode": "Channel Code",
+      "channelStatus": "Channel Status",
+      "weight": "Weight",
+      "priority": "Priority",
+      "minAmount": "Min Amount",
+      "maxAmount": "Max Amount",
+      "feeRate": "Fee Rate",
+      "fixedFee": "Fixed Fee",
+      "merchantId": "Merchant ID",
+      "secretKey": "Secret Key",
+      "apiUrl": "API URL",
+      "notifyUrl": "Notify URL",
+      "returnUrl": "Return URL",
+      "enabled": "Enabled",
+      "disabled": "Disabled",
+      "saveConfig": "Save Config",
+      "resetConfig": "Reset Config",
+      "testConnection": "Test Connection",
+      "configSaved": "Config saved successfully",
+      "configFailed": "Failed to save config",
+      "enterChannelName": "Please enter channel name",
+      "enterChannelCode": "Please enter channel code",
+      "enterMerchantId": "Please enter merchant ID",
+      "enterAppId": "Please enter App ID",
+      "enterSecretKey": "Please enter secret key",
+      "enterApiUrl": "Please enter API URL",
+      "selectPaymentMethod": "Please select payment method",
+      "channelNameRequired": "Please enter channel name",
+      "channelCodeRequired": "Please enter channel code",
+      "merchantIdRequired": "Please enter merchant ID",
+      "secretKeyRequired": "Please enter secret key",
+      "confirmDeleteChannel": "Confirm to delete this payment channel?",
+      "weightSaveSuccess": "Weight saved successfully",
+      "weightSaveFailed": "Failed to save weight",
+      "statusUpdateSuccess": "Status updated successfully",
+      "statusUpdateFailed": "Failed to update status",
+      "channelStatusUpdateSuccess": "Channel status updated successfully",
+      "channelStatusUpdateFailed": "Failed to update channel status"
+    },
+    "finance": {
+      "financeReport": "Finance Report",
+      "timeRange": "Time Range",
+      "exportReport": "Export Report",
+      "exporting": "Exporting...",
+      "serialNumber": "Serial No.",
+      "subjectName": "Subject Name",
+      "alias": "Alias",
+      "balanceDirection": "Balance Direction",
+      "initialBalance": "Initial Balance",
+      "debitAmount": "Debit Amount",
+      "creditAmount": "Credit Amount",
+      "endBalance": "End Balance",
+      "debit": "Debit",
+      "credit": "Credit",
+      "unknown": "Unknown",
+      "exportSuccess": "Export successful",
+      "exportFailed": "Export failed",
+      "noDataToExport": "No data to export",
+      "title": "Finance",
+      "commissionManagement": "Commission Management",
+      "financialReport": "Financial Report",
+      "walletBalance": "Wallet Balance",
+      "accountEarnings": "Account Earnings",
+      "unknownOperation": "Unknown Operation",
+      "unsettled": "Unsettled",
+      "settled": "Settled",
+      "recharge": "Recharge",
+      "withdraw": "Withdraw",
+      "groupPayment": "Group Payment",
+      "joinPayment": "Join Payment",
+      "incompleteRefund": "Incomplete Refund",
+      "completeRefund": "Complete Refund",
+      "groupRedPacketEarnings": "Group Red Packet Earnings",
+      "joinRedPacketEarnings": "Join Red Packet Earnings",
+      "signInRedPacketEarnings": "Sign-in Red Packet Earnings",
+      "subordinateRedPacketCommission": "Subordinate Red Packet Commission",
+      "subSubordinateRedPacketCommission": "Sub-subordinate Red Packet Commission",
+      "rechargeRebate": "Recharge Rebate",
+      "withdrawalFee": "Withdrawal Fee",
+      "unknownType": "Unknown Type",
+      "submitRechargeOrder": "Submit Recharge Order",
+      "rechargeCallThirdParty": "Recharge Call Third Party",
+      "rechargeCallbackSuccess": "Recharge Callback Success",
+      "submitWithdrawApplication": "Submit Withdraw Application",
+      "withdrawApproved": "Withdraw Approved",
+      "withdrawRejected": "Withdraw Rejected",
+      "withdrawCallThirdParty": "Withdraw Call Third Party",
+      "withdrawCallbackSuccess": "Withdraw Callback Success",
+      "confirmSuccess": "Confirm Success",
+      "batchConfirmSuccess": "Batch Confirm Success",
+      "batchApproveSuccess": "Batch Approve Success",
+      "batchRejectSuccess": "Batch Reject Success"
+    },
+    "commission": {
+      "title": "Commission Management",
+      "commissionNo": "Commission No.",
+      "userName": "User Name",
+      "phoneNo": "Phone No.",
+      "commissionAmount": "Commission Amount",
+      "commissionType": "Commission Type",
+      "commissionDesc": "Commission Description",
+      "orderNo": "Order No.",
+      "issueTime": "Issue Time",
+      "settleTime": "Settle Time",
+      "exportRecords": "Export Records",
+      "exporting": "Exporting...",
+      "commissionRecords": "Commission Records",
+      "enterUserName": "Please enter user name",
+      "enterPhoneNo": "Please enter phone number",
+      "enterOrderNo": "Please enter order number",
+      "selectStatus": "Please select status",
+      "selectType": "Please select type",
+      "selectTimeRange": "Please select time range",
+      "unsettled": "Unsettled",
+      "settled": "Settled",
+      "recharge": "Recharge",
+      "withdraw": "Withdraw",
+      "groupPayment": "Group Payment",
+      "joinPayment": "Join Payment",
+      "orderRefund": "Order Refund",
+      "commissionRefund": "Commission Refund",
+      "inviteReward": "Invite Reward",
+      "levelReward": "Level Reward",
+      "taskReward": "Task Reward",
+      "unknown": "Unknown",
+      "basicInfo": "Basic Information",
+      "transactionNo": "Transaction No.",
+      "transactionAmount": "Transaction Amount",
+      "balance": "Balance",
+      "createTime": "Create Time",
+      "updateTime": "Update Time",
+      "remark": "Remark",
+      "operationLog": "Operation Log",
+      "operator": "Operator",
+      "operationTime": "Operation Time",
+      "operationType": "Operation Type",
+      "operationDesc": "Operation Description",
+      "beforeStatus": "Before Status",
+      "afterStatus": "After Status",
+      "userId": "User ID",
+      "userInfo": "User Information",
+      "enterUserId": "Please enter user ID",
+      "selectCommissionType": "Please select commission type",
+      "orderCommission": "Order Commission",
+      "referralCommission": "Referral Commission",
+      "teamCommission": "Team Commission",
+      "sourceOrder": "Source Order",
+      "enterSourceOrderNo": "Please enter source order number",
+      "commissionRate": "Commission Rate",
+      "selectStatus": "Please select status",
+      "frozen": "Frozen",
+      "enterRemark": "Please enter remark",
+      "userDetail": "User Detail",
+      "orderDetail": "Order Detail"
+    },
+    "profile": {
+      "userInfo": "User Information",
+      "defaultAvatar": "Default Avatar",
+      "uploadAvatar": "Upload Avatar",
+      "userName": "User Name",
+      "enterUserName": "Please enter user name",
+      "loginAccount": "Login Account",
+      "enterLoginAccount": "Please enter login account",
+      "role": "Role",
+      "logout": "Logout",
+      "confirmLogout": "Are you sure you want to logout?"
+    },
+    "setting": {
+      "systemInfo": "System Information",
+      "appearance": "Appearance",
+      "selectTheme": "Select Theme",
+      "mode": "Mode",
+      "lightMode": "Light Mode",
+      "darkMode": "Dark Mode",
+      "followSystem": "Follow System",
+      "cache": "Cache",
+      "clearCache": "Clear Cache",
+      "confirmClearCache": "Are you sure to clear cache?"
+    },
+    "recharge": {
+      "basicInfo": "Basic Information",
+      "orderNo": "Recharge Order No",
+      "status": "Recharge Status",
+      "bank": "Recharge Bank",
+      "userName": "User Name",
+      "accountName": "Account Name",
+      "phoneNo": "Phone No.",
+      "channel": "Recharge Channel",
+      "method": "Recharge Method",
+      "account": "Recharge Account",
+      "walletBalance": "Wallet Balance",
+      "currency": "Currency",
+      "earningsBalance": "Earnings Balance",
+      "amount": "Recharge Amount",
+      "createTime": "Order Time",
+      "successTime": "Success Time",
+      "timeType": "Time Type",
+      "selectChannel": "Please select channel",
+      "selectTimeType": "Please select time type",
+      "bankTransfer": "Bank Transfer",
+      "onlinePayment": "Online Payment",
+      "mobilePayment": "Mobile Payment",
+      "walletPayment": "Wallet Payment",
+      "title": "Recharge Management",
+      "processing": "Processing",
+      "success": "Recharge Success",
+      "failed": "Recharge Failed",
+      "timeout": "Timeout Cancelled",
+      "exportRecords": "Export Records",
+      "exporting": "Exporting...",
+      "records": "Recharge Records",
+      "detail": "Recharge Detail",
+      "userDetail": "User Detail",
+      "enterUserName": "Please enter user name",
+      "enterPhoneNo": "Please enter phone number",
+      "enterOrderNo": "Please enter order number",
+      "timeRange": "Time Range",
+      "selectTimeRange": "Please select time range",
+      "operationLog": "Operation Log",
+      "operationTime": "Operation Time",
+      "operator": "Operator",
+      "operationType": "Operation Type",
+      "remark": "Remark"
+    },
+    "withdraw": {
+      "channel": "Withdraw Channel",
+      "method": "Withdraw Method",
+      "currency": "Currency",
+      "bankTransfer": "Bank Transfer",
+      "onlinePayment": "Online Payment",
+      "mobilePayment": "Mobile Payment",
+      "walletPayment": "Wallet Payment",
+      "title": "Withdraw Management",
+      "processing": "Processing",
+      "approved": "Approved",
+      "rejected": "Rejected",
+      "success": "Withdraw Success",
+      "failed": "Withdraw Failed",
+      "timeout": "Timeout Cancelled",
+      "exportRecords": "Export Records",
+      "exporting": "Exporting...",
+      "records": "Withdraw Records",
+      "detail": "Withdraw Detail",
+      "userDetail": "User Detail",
+      "orderNo": "Withdraw Order No",
+      "status": "Withdraw Status",
+      "userName": "User Name",
+      "phoneNo": "Phone No",
+      "accountType": "Account Type",
+      "amount": "Withdraw Amount",
+      "receivingBank": "Receiving Bank",
+      "receivingAccountName": "Receiving Account Name",
+      "receivingAccount": "Receiving Account",
+      "createTime": "Order Time",
+      "successTime": "Success Time",
+      "enterUserName": "Please enter user name",
+      "enterPhoneNo": "Please enter phone number",
+      "enterOrderNo": "Please enter order number",
+      "selectChannel": "Please select channel",
+      "timeType": "Time Type",
+      "selectTimeType": "Please select time type",
+      "timeRange": "Time Range",
+      "selectTimeRange": "Please select time range",
+      "basicInfo": "Basic Information",
+      "approve": "Approve",
+      "reject": "Reject",
+      "accountName": "Account Name",
+      "walletBalance": "Wallet Balance",
+      "earningsBalance": "Earnings Balance",
+      "operationLog": "Operation Log",
+      "operationTime": "Operation Time",
+      "operator": "Operator",
+      "operationType": "Operation Type",
+      "remark": "Remark",
+      "auditRemark": "Audit Remark",
+      "rejectReason": "Reject Reason",
+      "enterAuditRemark": "Please enter audit remark (optional)",
+      "enterRejectReason": "Please enter reject reason (required)",
+      "rejectReasonRequired": "Please enter reject reason",
+      "rejectReasonLength": "Reject reason length should be 1 to 200 characters",
+      "approveSuccess": "Approve Success",
+      "rejectSuccess": "Reject Success",
+      "auditFailed": "Audit Failed",
+      "accountType": "Account Type"
     },
     "dashboard": {
       "dashboard": "Dashboard",
@@ -668,7 +1076,15 @@
       "printOrder": "Print Order",
       "exportOrder": "Export Order",
       "exportOrders": "Export Orders",
-      "exportDeliveryList": "Export Delivery List"
+      "exportDeliveryList": "Export Delivery List",
+      "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"
     },
     "system": {
       "systemManagement": "System Management",
@@ -774,16 +1190,6 @@
       "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"
     }
   }
 }

+ 6 - 0
src/locales/navigation.js

@@ -53,6 +53,12 @@ export const navigationMap = {
   'admin.auth.role': 'menu.roleManagement',
   'admin.banner': 'menu.carouselBanner',
   'admin.payment': 'menu.paymentConfig',
+
+  // 后台管理模块
+  admin: 'menu.adminManagement',
+  'admin.profile': 'menu.profile',
+  'admin.auth': 'menu.authSettings',
+  'admin.notification': 'menu.notification',
 };
 
 /**

+ 416 - 13
src/locales/zh-CN/index.json

@@ -1,4 +1,5 @@
 {
+  "locale": "zh-CN",
   "common": {
     "save": "保存",
     "update": "更新",
@@ -89,7 +90,47 @@
     "detail": "详情",
     "unknown": "未知",
     "fetchDataFailed": "获取数据失败",
-    "createTime": "创建时间"
+    "createTime": "创建时间",
+    "title": "标题",
+    "identifier": "标识",
+    "enterUniqueIdentifier": "请输入唯一标识",
+    "enterShortPath": "请输入短路径",
+    "weight": "权重",
+    "enterWeight": "请输入权重",
+    "normal": "正常",
+    "disabled": "禁用",
+    "includePermissions": "包含权限",
+    "name": "名称",
+    "operation": "操作",
+    "enterName": "请输入名称",
+    "enterIdentifier": "请输入标识",
+    "addTemplate": "添加模板",
+    "selectIcon": "选择图标",
+    "uploadCert": "上传证书",
+    "noInfo": "暂无信息",
+    "cancelled": "已取消",
+    "loading": "加载中...",
+    "sort": "排序",
+    "enterSort": "请填写排序",
+    "actions": "操作",
+    "add": "新建",
+    "save": "保存",
+    "update": "更新",
+    "to": "至",
+    "startDate": "开始日期",
+    "endDate": "结束日期",
+    "status": "状态",
+    "all": "全部",
+    "confirm": "确定",
+    "cancel": "取消",
+    "cancelled": "已取消",
+    "tip": "提示",
+    "unknown": "未知",
+    "actions": "操作",
+    "detail": "详情",
+    "loading": "加载中...",
+    "fetchDataFailed": "获取数据失败",
+    "submit": "提交"
   },
   "menu": {
     "dashboard": "首页",
@@ -137,7 +178,23 @@
     "staffManagement": "人员管理",
     "roleManagement": "角色管理",
     "carouselBanner": "轮播图",
-    "paymentConfig": "支付配置"
+    "paymentConfig": "支付配置",
+    "menu": "菜单",
+    "page": "页面",
+    "action": "动作",
+    "adminManagement": "后台管理",
+    "authSettings": "权限设置",
+    "notification": "消息通知"
+  },
+  "taskbar": {
+    "close": "关闭",
+    "closeOthers": "关闭其它",
+    "profile": "个人中心",
+    "changePassword": "修改密码",
+    "logout": "退出登录",
+    "confirmLogout": "您确定要退出登录吗?",
+    "logoutSuccess": "退出成功",
+    "superAdminCannotChangePassword": "超级管理员无法修改密码"
   },
   "form": {
     "name": "名称",
@@ -290,6 +347,10 @@
     "confirmBatchOffSale": "此操作将批量下架商品,是否继续?",
     "tip": "提示",
     "selectionLimitReached": "已到选择上限",
+    "fetchRoleListFailed": "获取角色列表失败",
+    "fetchDetailFailed": "获取详情失败",
+    "submitFailed": "提交失败",
+    "changePasswordFailed": "修改密码失败",
     "maxUploadLimit": "最多只能上传{count}张图片",
     "uploadError": "上传失败",
     "unsupportedFormat": "不支持{extension}格式,请选择{formats}格式的图片",
@@ -378,9 +439,352 @@
       "confirmPasswordRequired": "请确认密码",
       "roleRequired": "请选择角色",
       "normal": "正常",
+      "application": "应用",
+      "selectAll": "全选",
+      "addPermission": "+添加",
+      "initMenu": "初始化菜单",
+      "noData": "暂无数据",
+      "confirmDeleteRecord": "确认删除这条记录?",
+      "confirmInit": "此操作将清空所有现有权限并重新初始化菜单权限,是否继续?\n⚠️ 注意:此操作不可逆!",
+      "initConfirmTitle": "初始化确认",
+      "initSuccess": "菜单权限初始化成功!",
+      "initFailed": "初始化失败: {message}",
+      "createPermissionSuccess": "创建权限成功",
+      "createPermissionFailed": "创建权限失败",
+      "updateSortSuccess": "更新排序成功",
+      "updateSortFailed": "更新排序失败",
+      "deletePermissionSuccess": "删除权限成功",
+      "deletePermissionFailed": "删除权限失败",
+      "parentPermission": "父级权限",
+      "icon": "图标",
+      "sort": "排序",
+      "url": "链接地址",
+      "isAction": "是否为操作权限",
+      "assignPermissions": "分配权限",
+      "permissionTree": "权限树",
+      "normalTip": "该权限正常可用",
+      "disabledTip": "禁用该权限后,除了超级管理员都将无法访问此权限",
       "disabled": "禁用",
       "locked": "锁定"
     },
+    "banner": {
+      "title": "轮播图管理",
+      "bannerTitle": "轮播图标题",
+      "image": "图片",
+      "bannerImage": "轮播图片",
+      "linkType": "链接类型",
+      "linkAddress": "链接地址",
+      "internalLink": "内链",
+      "externalLink": "外链",
+      "addBanner": "新建轮播图",
+      "editBanner": "编辑轮播图",
+      "enterBannerTitle": "请输入轮播图标题",
+      "uploadImage": "上传图片",
+      "enterLinkAddress": "请输入链接地址",
+      "titleRequired": "请输入轮播图标题",
+      "imageRequired": "请上传轮播图片",
+      "linkRequired": "请输入链接地址"
+    },
+    "payment": {
+      "title": "支付配置",
+      "collectionConfig": "代收配置",
+      "payoutConfig": "代付配置",
+      "paymentChannel": "支付通道",
+      "addChannel": "新增通道",
+      "addPaymentChannel": "新增支付通道",
+      "editPaymentChannel": "编辑支付通道",
+      "collectionMethod": "代收支付方式",
+      "collectionChannel": "代收支付通道",
+      "payoutMethod": "代付支付方式",
+      "payoutChannel": "代付支付通道",
+      "channelName": "通道名称",
+      "channelCode": "通道代码",
+      "channelStatus": "通道状态",
+      "weight": "权重",
+      "priority": "优先级",
+      "minAmount": "最小金额",
+      "maxAmount": "最大金额",
+      "feeRate": "费率",
+      "fixedFee": "固定费用",
+      "merchantId": "商户号",
+      "secretKey": "密钥",
+      "apiUrl": "接口地址",
+      "notifyUrl": "回调地址",
+      "returnUrl": "返回地址",
+      "enabled": "启用",
+      "disabled": "禁用",
+      "saveConfig": "保存配置",
+      "resetConfig": "重置配置",
+      "testConnection": "测试连接",
+      "configSaved": "配置保存成功",
+      "configFailed": "配置保存失败",
+      "enterChannelName": "请输入通道名称",
+      "enterChannelCode": "请输入通道代码",
+      "enterMerchantId": "请输入商户号",
+      "enterAppId": "请输入应用ID",
+      "enterSecretKey": "请输入密钥",
+      "enterApiUrl": "请输入接口地址",
+      "selectPaymentMethod": "请选择支付方式",
+      "channelNameRequired": "请输入通道名称",
+      "channelCodeRequired": "请输入通道代码",
+      "merchantIdRequired": "请输入商户号",
+      "secretKeyRequired": "请输入密钥",
+      "confirmDeleteChannel": "确认删除这个支付通道?",
+      "weightSaveSuccess": "权重保存成功",
+      "weightSaveFailed": "权重保存失败",
+      "statusUpdateSuccess": "状态更新成功",
+      "statusUpdateFailed": "状态更新失败",
+      "channelStatusUpdateSuccess": "通道状态更新成功",
+      "channelStatusUpdateFailed": "通道状态更新失败"
+    },
+    "finance": {
+      "financeReport": "财务报表",
+      "timeRange": "时间范围",
+      "exportReport": "导出报表",
+      "exporting": "导出中...",
+      "serialNumber": "序号",
+      "subjectName": "科目名称",
+      "alias": "别名",
+      "balanceDirection": "余额方向",
+      "initialBalance": "初始日余额",
+      "debitAmount": "借方发生额",
+      "creditAmount": "贷方发生额",
+      "endBalance": "日余额",
+      "debit": "借",
+      "credit": "贷",
+      "unknown": "未知",
+      "exportSuccess": "导出成功",
+      "exportFailed": "导出失败",
+      "noDataToExport": "暂无数据可导出"
+    },
+    "commission": {
+      "title": "佣金管理",
+      "commissionNo": "佣金单号",
+      "userName": "用户名",
+      "phoneNo": "手机号",
+      "commissionAmount": "佣金金额",
+      "commissionType": "佣金类型",
+      "commissionDesc": "佣金说明",
+      "orderNo": "订单号",
+      "issueTime": "发放时间",
+      "settleTime": "到账时间",
+      "exportRecords": "导出记录",
+      "exporting": "导出中...",
+      "commissionRecords": "佣金记录",
+      "enterUserName": "请输入用户名",
+      "enterPhoneNo": "请输入手机号",
+      "enterOrderNo": "请输入订单号",
+      "selectStatus": "请选择状态",
+      "selectType": "请选择类型",
+      "selectTimeRange": "请选择时间范围",
+      "unsettled": "未结算",
+      "settled": "已结算",
+      "recharge": "充值",
+      "withdraw": "提现",
+      "groupPayment": "开团支付",
+      "joinPayment": "参团支付",
+      "orderRefund": "订单退款",
+      "commissionRefund": "佣金退款",
+      "inviteReward": "邀请奖励",
+      "levelReward": "等级奖励",
+      "taskReward": "任务奖励",
+      "unknown": "未知",
+      "basicInfo": "基本信息",
+      "transactionNo": "交易号",
+      "transactionAmount": "交易金额",
+      "balance": "余额",
+      "createTime": "创建时间",
+      "updateTime": "更新时间",
+      "remark": "备注",
+      "operationLog": "操作日志",
+      "operator": "操作人",
+      "operationTime": "操作时间",
+      "operationType": "操作类型",
+      "operationDesc": "操作说明",
+      "beforeStatus": "操作前状态",
+      "afterStatus": "操作后状态",
+      "userId": "用户ID",
+      "userInfo": "用户信息",
+      "enterUserId": "请填写用户ID",
+      "selectCommissionType": "请选择佣金类型",
+      "orderCommission": "订单佣金",
+      "referralCommission": "推荐佣金",
+      "teamCommission": "团队佣金",
+      "sourceOrder": "来源订单",
+      "enterSourceOrderNo": "请填写来源订单号",
+      "commissionRate": "佣金比例",
+      "selectStatus": "请选择状态",
+      "frozen": "已冻结",
+      "enterRemark": "请填写备注",
+      "userDetail": "用户详情",
+      "orderDetail": "订单详情"
+    },
+    "profile": {
+      "userInfo": "用户信息",
+      "defaultAvatar": "默认头像",
+      "uploadAvatar": "上传头像",
+      "userName": "用户名称",
+      "enterUserName": "请输入用户名称",
+      "loginAccount": "登录账号",
+      "enterLoginAccount": "请输入登录账号",
+      "role": "角色",
+      "logout": "退出登录",
+      "confirmLogout": "您确定要退出登录吗?"
+    },
+    "setting": {
+      "systemInfo": "系统信息",
+      "appearance": "外观",
+      "selectTheme": "选择主题",
+      "mode": "模式",
+      "lightMode": "明亮模式",
+      "darkMode": "暗黑模式",
+      "followSystem": "跟随系统",
+      "cache": "缓存",
+      "clearCache": "清除缓存",
+      "confirmClearCache": "确定清除缓存?"
+    },
+    "recharge": {
+      "basicInfo": "基本信息",
+      "orderNo": "充值单号",
+      "status": "充值状态",
+      "bank": "充值银行",
+      "userName": "用户名",
+      "accountName": "账户名称",
+      "phoneNo": "手机号",
+      "channel": "充值通道",
+      "method": "充值方式",
+      "account": "充值账户",
+      "walletBalance": "账户余额",
+      "currency": "币种",
+      "earningsBalance": "收益余额",
+      "amount": "充值金额",
+      "createTime": "下单时间",
+      "successTime": "成功时间",
+      "timeType": "时间类型",
+      "selectChannel": "请选择通道",
+      "selectTimeType": "请选择时间类型",
+      "bankTransfer": "银行转账",
+      "onlinePayment": "在线支付",
+      "mobilePayment": "手机支付",
+      "walletPayment": "钱包支付",
+      "title": "充值管理",
+      "processing": "处理中",
+      "success": "充值成功",
+      "failed": "充值失败",
+      "timeout": "超时取消",
+      "exportRecords": "导出记录",
+      "exporting": "导出中...",
+      "records": "充值记录",
+      "detail": "充值详情",
+      "userDetail": "用户详情",
+      "enterUserName": "请输入用户名",
+      "enterPhoneNo": "请输入手机号",
+      "enterOrderNo": "请输入订单号",
+      "timeRange": "时间范围",
+      "selectTimeRange": "请选择时间范围",
+      "operationLog": "操作日志",
+      "operationTime": "操作时间",
+      "operator": "操作人",
+      "operationType": "操作类型",
+      "remark": "备注"
+    },
+    "withdraw": {
+      "channel": "提款通道",
+      "method": "提款方式",
+      "currency": "币种",
+      "bankTransfer": "银行转账",
+      "onlinePayment": "在线支付",
+      "mobilePayment": "手机支付",
+      "walletPayment": "钱包支付",
+      "title": "提款管理",
+      "processing": "处理中",
+      "approved": "审核通过",
+      "rejected": "审核拒绝",
+      "success": "提现成功",
+      "failed": "提现失败",
+      "timeout": "超时取消",
+      "exportRecords": "导出记录",
+      "exporting": "导出中...",
+      "records": "提现记录",
+      "detail": "提款详情",
+      "userDetail": "用户详情",
+      "orderNo": "提款单号",
+      "status": "提款状态",
+      "userName": "用户名",
+      "phoneNo": "手机号",
+      "accountType": "提款类型",
+      "amount": "提款金额",
+      "receivingBank": "收款银行",
+      "receivingAccountName": "收款账户名称",
+      "receivingAccount": "收款账户",
+      "createTime": "下单时间",
+      "successTime": "成功时间",
+      "enterUserName": "请输入用户名",
+      "enterPhoneNo": "请输入手机号",
+      "enterOrderNo": "请输入订单号",
+      "selectChannel": "请选择通道",
+      "timeType": "时间类型",
+      "selectTimeType": "请选择时间类型",
+      "timeRange": "时间范围",
+      "selectTimeRange": "请选择时间范围",
+      "basicInfo": "基本信息",
+      "approve": "审核通过",
+      "reject": "审核拒绝",
+      "accountName": "账户名称",
+      "walletBalance": "账户余额",
+      "earningsBalance": "收益余额",
+      "operationLog": "操作日志",
+      "operationTime": "操作时间",
+      "operator": "操作人",
+      "operationType": "操作类型",
+      "remark": "备注",
+      "auditRemark": "审核备注",
+      "rejectReason": "拒绝原因",
+      "enterAuditRemark": "请输入审核备注(可选)",
+      "enterRejectReason": "请输入拒绝原因(必填)",
+      "rejectReasonRequired": "请输入拒绝原因",
+      "rejectReasonLength": "拒绝原因长度在1到200个字符",
+      "approveSuccess": "审核通过成功",
+      "rejectSuccess": "审核拒绝成功",
+      "auditFailed": "审核失败",
+      "accountType": "账户类型"
+    },
+    "finance": {
+      "title": "财务",
+      "commissionManagement": "佣金管理",
+      "financialReport": "财务报表",
+      "walletBalance": "钱包余额",
+      "accountEarnings": "账户收益",
+      "unknownOperation": "未知操作",
+      "unsettled": "未结算",
+      "settled": "已结算",
+      "recharge": "充值",
+      "withdraw": "提现",
+      "groupPayment": "开团支付",
+      "joinPayment": "参团支付",
+      "incompleteRefund": "未成团退款",
+      "completeRefund": "成团退款",
+      "groupRedPacketEarnings": "开团红包收益",
+      "joinRedPacketEarnings": "参团红包收益",
+      "signInRedPacketEarnings": "签到红包收益",
+      "subordinateRedPacketCommission": "下级红包佣金",
+      "subSubordinateRedPacketCommission": "下下级红包佣金",
+      "rechargeRebate": "充值返点",
+      "withdrawalFee": "提现手续费",
+      "unknownType": "未知类型",
+      "submitRechargeOrder": "提交充值订单",
+      "rechargeCallThirdParty": "充值调用第三方",
+      "rechargeCallbackSuccess": "充值回调通知成功",
+      "submitWithdrawApplication": "提交提现申请",
+      "withdrawApproved": "提现审核通过",
+      "withdrawRejected": "提现审核失败",
+      "withdrawCallThirdParty": "提现调用第三方",
+      "withdrawCallbackSuccess": "提现回调通知成功",
+      "confirmSuccess": "确认成功",
+      "batchConfirmSuccess": "批量确认成功",
+      "batchApproveSuccess": "批量通过成功",
+      "batchRejectSuccess": "批量拒绝成功"
+    },
     "dashboard": {
       "dashboard": "仪表盘",
       "overview": "概览",
@@ -635,6 +1039,7 @@
       "goodsQuantity": "商品数量",
       "quantity": "数量",
       "paymentAmount": "付款金额",
+      "paymentStatus": "付款状态",
       "paid": "已付款",
       "pendingPayment": "待付款",
       "groupStatus": "拼团状态",
@@ -673,7 +1078,15 @@
       "printOrder": "打印订单",
       "exportOrder": "导出订单",
       "exportOrders": "订单导出",
-      "exportDeliveryList": "导出发货单"
+      "exportDeliveryList": "导出发货单",
+      "importDeliveryTip": "导入发货单,系统将自动处理发货信息",
+      "importDeliveryFile": "导入发货单",
+      "reSelectFile": "重新选择文件",
+      "batchDispatch": "批量发货",
+      "pleaseSelectFile": "请先选择发货单文件",
+      "batchDispatchSuccess": "批量发货成功",
+      "batchDispatchFailed": "批量发货失败",
+      "partialDispatchFailed": "部分发货失败,已下载失败记录"
     },
     "system": {
       "systemManagement": "系统管理",
@@ -779,16 +1192,6 @@
       "endTimeRequired": "请选择结束时间",
       "groupSizeRequired": "请选择成团默认人数",
       "countdownTimeRequired": "请选择开团倒计时结束"
-    },
-    "order": {
-      "importDeliveryTip": "导入发货单,系统将自动处理发货信息",
-      "importDeliveryFile": "导入发货单",
-      "reSelectFile": "重新选择文件",
-      "batchDispatch": "批量发货",
-      "pleaseSelectFile": "请先选择发货单文件",
-      "batchDispatchSuccess": "批量发货成功",
-      "batchDispatchFailed": "批量发货失败",
-      "partialDispatchFailed": "部分发货失败,已下载失败记录"
     }
   }
 }

+ 235 - 231
src/sheep/components/sa-table/sa-search/sa-search-simple.global.vue

@@ -8,113 +8,55 @@
           <template v-for="(field, key, index) in searchFields" :key="key">
             <el-form-item v-if="index < 3" :prop="key" :label="field.label">
               <!-- 输入框类型 -->
-              <el-input
-                v-if="field.type === 'input'"
-                v-model="formData[key]"
-                :placeholder="field.placeholder || ''"
-                clearable
-                :style="{ width: field.width ? field.width + 'px' : '200px' }"
-              />
+              <el-input v-if="field.type === 'input'" v-model="formData[key]" :placeholder="field.placeholder || ''"
+                clearable :style="{ width: field.width ? field.width + 'px' : '200px' }" />
 
               <!-- 下拉选择类型 -->
-              <el-select
-                v-else-if="field.type === 'select'"
-                v-model="formData[key]"
-                :placeholder="field.placeholder || field.label"
-                clearable
-                :style="{ width: field.width ? field.width + 'px' : '200px' }"
-              >
-                <el-option
-                  v-for="option in field.options"
-                  :key="option.value"
-                  :label="option.label"
-                  :value="option.value"
-                />
+              <el-select v-else-if="field.type === 'select'" v-model="formData[key]"
+                :placeholder="field.placeholder || field.label" clearable
+                :style="{ width: field.width ? field.width + 'px' : '200px' }">
+                <el-option v-for="option in field.options" :key="option.value" :label="option.label"
+                  :value="option.value" />
               </el-select>
 
               <!-- 级联选择类型 -->
-              <el-cascader
-                v-else-if="field.type === 'cascader'"
-                v-model="formData[key]"
-                :options="field.options"
-                :props="field.props"
-                :placeholder="field.placeholder || field.label"
-                clearable
-                :style="{ width: field.width ? field.width + 'px' : '300px' }"
-              />
+              <el-cascader v-else-if="field.type === 'cascader'" v-model="formData[key]" :options="field.options"
+                :props="field.props" :placeholder="field.placeholder || field.label" clearable
+                :style="{ width: field.width ? field.width + 'px' : '300px' }" />
 
               <!-- 日期范围类型 -->
-              <el-date-picker
-                v-else-if="field.type === 'daterange'"
-                v-model="formData[key]"
-                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: field.width ? field.width + 'px' : '350px' }"
-              />
+              <el-date-picker v-else-if="field.type === 'daterange'" v-model="formData[key]" 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: field.width ? field.width + 'px' : '350px' }" />
             </el-form-item>
           </template>
 
           <!-- 展开时显示的额外搜索项 -->
-          <template
-            v-if="isExpanded"
-            v-for="(field, key, index) in searchFields"
-            :key="'expanded-' + key"
-          >
+          <template v-if="isExpanded" v-for="(field, key, index) in searchFields" :key="'expanded-' + key">
             <el-form-item v-if="index >= 3" :prop="key" :label="field.label">
               <!-- 输入框类型 -->
-              <el-input
-                v-if="field.type === 'input'"
-                v-model="formData[key]"
-                :placeholder="field.placeholder || ''"
-                clearable
-                :style="{ width: field.width ? field.width + 'px' : '200px' }"
-              />
+              <el-input v-if="field.type === 'input'" v-model="formData[key]" :placeholder="field.placeholder || ''"
+                clearable :style="{ width: field.width ? field.width + 'px' : '200px' }" />
 
               <!-- 下拉选择类型 -->
-              <el-select
-                v-else-if="field.type === 'select'"
-                v-model="formData[key]"
-                :placeholder="field.placeholder || field.label"
-                clearable
-                :style="{ width: field.width ? field.width + 'px' : '200px' }"
-              >
-                <el-option
-                  v-for="option in field.options"
-                  :key="option.value"
-                  :label="option.label"
-                  :value="option.value"
-                />
+              <el-select v-else-if="field.type === 'select'" v-model="formData[key]"
+                :placeholder="field.placeholder || field.label" clearable
+                :style="{ width: field.width ? field.width + 'px' : '200px' }">
+                <el-option v-for="option in field.options" :key="option.value" :label="option.label"
+                  :value="option.value" />
               </el-select>
 
               <!-- 级联选择类型 -->
-              <el-cascader
-                v-else-if="field.type === 'cascader'"
-                v-model="formData[key]"
-                :options="field.options"
-                :props="field.props"
-                :placeholder="field.placeholder || field.label"
-                clearable
-                :style="{ width: field.width ? field.width + 'px' : '300px' }"
-              />
+              <el-cascader v-else-if="field.type === 'cascader'" v-model="formData[key]" :options="field.options"
+                :props="field.props" :placeholder="field.placeholder || field.label" clearable
+                :style="{ width: field.width ? field.width + 'px' : '300px' }" />
 
               <!-- 日期范围类型 -->
-              <el-date-picker
-                v-else-if="field.type === 'daterange'"
-                v-model="formData[key]"
-                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: field.width ? field.width + 'px' : '350px' }"
-              />
+              <el-date-picker v-else-if="field.type === 'daterange'" v-model="formData[key]" 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: field.width ? field.width + 'px' : '350px' }" />
             </el-form-item>
           </template>
 
@@ -139,54 +81,124 @@
 </template>
 
 <script>
-  import { ref, computed, reactive, watch } from 'vue';
-  import { ArrowDown } from '@element-plus/icons-vue';
-
-  export default {
-    name: 'SaSearchSimple',
-    components: {
-      ArrowDown,
-    },
-  };
+import { ref, computed, reactive, watch } from 'vue';
+import { ArrowDown } from '@element-plus/icons-vue';
+
+export default {
+  name: 'SaSearchSimple',
+  components: {
+    ArrowDown,
+  },
+};
 </script>
 
 <script setup>
-  import { useI18n } from 'vue-i18n';
-
-  const { t } = useI18n();
-
-  const props = defineProps({
-    searchFields: {
-      type: Object,
-      default: () => ({}),
-    },
-    defaultValues: {
-      type: Object,
-      default: () => ({}),
-    },
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
+
+const props = defineProps({
+  searchFields: {
+    type: Object,
+    default: () => ({}),
+  },
+  defaultValues: {
+    type: Object,
+    default: () => ({}),
+  },
+  // 支持 v-model 双向绑定
+  modelValue: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+
+const emit = defineEmits(['search', 'reset', 'update:modelValue']);
+
+const formRef = ref();
+const isExpanded = ref(false);
+const isInitialized = ref(false);
+
+// 表单数据
+const formData = reactive({});
+
+// 计算是否有超过3个搜索条件
+const hasMoreThanThree = computed(() => {
+  return Object.keys(props.searchFields).length > 3;
+});
+
+// 展开收起切换
+const toggleExpand = () => {
+  isExpanded.value = !isExpanded.value;
+};
+
+// 搜索处理
+const handleSearch = () => {
+  // 过滤掉空值
+  const searchData = {};
+  Object.keys(formData).forEach((key) => {
+    const value = formData[key];
+    if (value !== '' && value !== null && value !== undefined) {
+      // 如果是数组且不为空
+      if (Array.isArray(value) && value.length > 0) {
+        searchData[key] = value;
+      }
+      // 如果是对象且有有效值
+      else if (typeof value === 'object' && value !== null) {
+        if (value.min || value.max) {
+          searchData[key] = value;
+        }
+      }
+      // 其他非空值
+      else if (value) {
+        searchData[key] = value;
+      }
+    }
   });
 
-  const emit = defineEmits(['search', 'reset']);
-
-  const formRef = ref();
-  const isExpanded = ref(false);
-
-  // 表单数据
-  const formData = reactive({});
-
-  // 计算是否有超过3个搜索条件
-  const hasMoreThanThree = computed(() => {
-    return Object.keys(props.searchFields).length > 3;
+  // 触发搜索事件(modelValue 已经通过 watch 自动更新了)
+  emit('search', searchData);
+};
+
+// 重置处理
+const handleReset = () => {
+  // 重置表单数据
+  Object.keys(formData).forEach((key) => {
+    if (props.defaultValues[key] !== undefined) {
+      formData[key] = props.defaultValues[key];
+    } else {
+      formData[key] = '';
+    }
   });
 
-  // 展开收起切换
-  const toggleExpand = () => {
-    isExpanded.value = !isExpanded.value;
-  };
-
-  // 搜索处理
-  const handleSearch = () => {
-    // 过滤掉空值
+  // 触发重置事件(modelValue 会通过 watch 自动更新)
+  emit('reset');
+};
+
+// 初始化表单数据
+const initFormData = () => {
+  isInitialized.value = false;
+  Object.keys(props.searchFields).forEach((key) => {
+    if (props.defaultValues[key] !== undefined) {
+      formData[key] = props.defaultValues[key];
+    } else {
+      formData[key] = '';
+    }
+  });
+  // 初始化完成后,设置标志
+  setTimeout(() => {
+    isInitialized.value = true;
+  }, 0);
+};
+
+// 监听表单数据变化,实时更新 modelValue
+watch(
+  formData,
+  () => {
+    // 只在初始化完成后才触发更新
+    if (!isInitialized.value) return;
+
+    // 过滤掉空值,实时更新父组件
     const searchData = {};
     Object.keys(formData).forEach((key) => {
       const value = formData[key];
@@ -208,133 +220,125 @@
       }
     });
 
-    emit('search', searchData);
-  };
-
-  // 重置处理
-  const handleReset = () => {
-    // 重置表单数据
-    Object.keys(formData).forEach((key) => {
-      if (props.defaultValues[key] !== undefined) {
-        formData[key] = props.defaultValues[key];
-      } else {
-        formData[key] = '';
-      }
-    });
-
-    emit('reset');
-  };
-
-  // 初始化表单数据
-  const initFormData = () => {
-    Object.keys(props.searchFields).forEach((key) => {
-      if (props.defaultValues[key] !== undefined) {
-        formData[key] = props.defaultValues[key];
-      } else {
-        formData[key] = '';
-      }
-    });
-  };
-
-  // 监听搜索字段变化
-  watch(
-    () => props.searchFields,
-    () => {
-      initFormData();
-    },
-    { immediate: true },
-  );
-
-  // 监听默认值变化
-  watch(
-    () => props.defaultValues,
-    () => {
-      initFormData();
-    },
-    { deep: true },
-  );
+    emit('update:modelValue', searchData);
+  },
+  { deep: true },
+);
+
+// 监听 modelValue 变化,支持外部更新
+watch(
+  () => props.modelValue,
+  (newValue) => {
+    if (newValue && typeof newValue === 'object') {
+      Object.keys(formData).forEach((key) => {
+        if (newValue[key] !== undefined) {
+          formData[key] = newValue[key];
+        }
+      });
+    }
+  },
+  { deep: true },
+);
+
+// 监听搜索字段变化
+watch(
+  () => props.searchFields,
+  () => {
+    initFormData();
+  },
+  { immediate: true },
+);
+
+// 监听默认值变化
+watch(
+  () => props.defaultValues,
+  () => {
+    initFormData();
+  },
+  { deep: true },
+);
 </script>
 
 <style lang="scss" scoped>
-  .sa-search-simple {
-    width: 100%;
-
-    .search-form {
-      margin-top: 30px;
-      background-color: #fff;
-      border-radius: 4px;
-
-      :deep(.el-form-item) {
-        margin-bottom: 16px;
-        margin-right: 16px;
-        min-width: fit-content; // 确保表单项不会被过度压缩
-      }
+.sa-search-simple {
+  width: 100%;
+
+  .search-form {
+    margin-top: 30px;
+    background-color: #fff;
+    border-radius: 4px;
+
+    :deep(.el-form-item) {
+      margin-bottom: 16px;
+      margin-right: 16px;
+      min-width: fit-content; // 确保表单项不会被过度压缩
+    }
 
-      .search-form-main {
-        display: flex;
-        flex-wrap: wrap;
-        align-items: flex-end;
-        margin-bottom: 0;
-      }
+    .search-form-main {
+      display: flex;
+      flex-wrap: wrap;
+      align-items: flex-end;
+      margin-bottom: 0;
+    }
 
-      .expand-toggle-btn {
-        margin-left: 8px;
-        transition: all 0.3s ease;
+    .expand-toggle-btn {
+      margin-left: 8px;
+      transition: all 0.3s ease;
 
-        .expand-toggle-icon {
-          margin-left: 4px;
-          transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+      .expand-toggle-icon {
+        margin-left: 4px;
+        transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
 
-          &.is-expanded {
-            transform: rotate(180deg);
-          }
+        &.is-expanded {
+          transform: rotate(180deg);
         }
+      }
 
-        &:hover {
-          color: #66b1ff;
-          transform: translateY(-1px);
-        }
+      &:hover {
+        color: #66b1ff;
+        transform: translateY(-1px);
+      }
 
-        &:active {
-          transform: translateY(0);
-        }
+      &:active {
+        transform: translateY(0);
       }
     }
   }
+}
 
-  // 响应式设计
-  @media (max-width: 768px) {
-    .sa-search-simple {
-      .search-form {
-        padding: 12px;
+// 响应式设计
+@media (max-width: 768px) {
+  .sa-search-simple {
+    .search-form {
+      padding: 12px;
 
-        :deep(.el-form-item) {
-          margin-bottom: 12px;
-          margin-right: 12px;
-        }
+      :deep(.el-form-item) {
+        margin-bottom: 12px;
+        margin-right: 12px;
+      }
 
-        .search-form-main {
-          flex-direction: column;
-          align-items: stretch;
+      .search-form-main {
+        flex-direction: column;
+        align-items: stretch;
 
-          :deep(.el-form-item) {
-            margin-right: 0;
-          }
+        :deep(.el-form-item) {
+          margin-right: 0;
         }
       }
     }
   }
+}
 
-  @media (max-width: 480px) {
-    .sa-search-simple {
-      .search-form {
-        padding: 8px;
+@media (max-width: 480px) {
+  .sa-search-simple {
+    .search-form {
+      padding: 8px;
 
-        :deep(.el-form-item) {
-          margin-bottom: 8px;
-          margin-right: 8px;
-        }
+      :deep(.el-form-item) {
+        margin-bottom: 8px;
+        margin-right: 8px;
       }
     }
   }
+}
 </style>

+ 25 - 22
src/sheep/components/sa-uploader/uploader-cert.vue

@@ -3,38 +3,41 @@
     <el-input v-model="inputUrl" :placeholder="placeholder" disabled>
       <template #append>
         <el-upload action :http-request="uploadFile" :show-file-list="false" accept=".pem, .crt">
-          <el-button type="primary">上传证书</el-button>
+          <el-button type="primary">{{ t('common.uploadCert') }}</el-button>
         </el-upload>
       </template>
     </el-input>
   </div>
 </template>
 <script>
-  export default {
-    name: 'UploaderCert',
-  };
+export default {
+  name: 'UploaderCert',
+};
 </script>
 <script setup>
-  import adminApi from '@/app/admin/api';
-  import { ref, watch } from 'vue';
+import adminApi from '@/app/admin/api';
+import { ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
 
-  const emit = defineEmits(['updateValue']);
-  const props = defineProps(['url', 'placeholder']);
+const { t } = useI18n();
 
-  const inputUrl = ref(props.url);
-  watch(
-    () => props.url,
-    () => {
-      inputUrl.value = props.url;
-    },
-  );
+const emit = defineEmits(['updateValue']);
+const props = defineProps(['url', 'placeholder']);
 
-  async function uploadFile(file) {
-    let formData = new FormData();
-    formData.append('file', file.file);
-    const { code, data } = await adminApi.file.upload({}, formData);
-    if (code == '200') {
-      emit('updateValue', data.path);
-    }
+const inputUrl = ref(props.url);
+watch(
+  () => props.url,
+  () => {
+    inputUrl.value = props.url;
+  },
+);
+
+async function uploadFile(file) {
+  let formData = new FormData();
+  formData.append('file', file.file);
+  const { code, data } = await adminApi.file.upload({}, formData);
+  if (code == '200') {
+    emit('updateValue', data.path);
   }
+}
 </script>

+ 99 - 116
src/sheep/components/sa-uploader/uploader-input.vue

@@ -1,149 +1,132 @@
 <template>
   <div class="uploader-input">
-    <uploader-item
-      :urlList="urlList.data"
-      :multiple="multiple"
-      :fileType="fileType"
-      :size="size"
-      :isCropper="isCropper"
-      @action="onUpdateValue"
-    ></uploader-item>
+    <uploader-item :urlList="urlList.data" :multiple="multiple" :fileType="fileType" :size="size" :isCropper="isCropper"
+      @action="onUpdateValue"></uploader-item>
     <div class="input-item sa-flex">
-      <el-input
-        class="url-input"
-        v-model="inputUrl"
-        @change="emit('updateValue', inputUrl)"
-      ></el-input>
-      <div class="svg" @click.stop="onSelect">选择图标</div>
-      <uploader-action
-        :urlList="urlList.data"
-        :action="action"
-        :savelog="savelog"
-        :multiple="multiple"
-        :max="max"
-        :fileType="fileType"
-        :size="size"
-        :filesize="filesize"
-        @action="onUpdateValue"
-        @success="onSuccess"
-      >
+      <el-input class="url-input" v-model="inputUrl" @change="emit('updateValue', inputUrl)"></el-input>
+      <div class="svg" @click.stop="onSelect">{{ t('common.selectIcon') }}</div>
+      <uploader-action :urlList="urlList.data" :action="action" :savelog="savelog" :multiple="multiple" :max="max"
+        :fileType="fileType" :size="size" :filesize="filesize" @action="onUpdateValue" @success="onSuccess">
         <template #action>
-          <div class="image">上传图片</div>
+          <div class="image">{{ t('common.uploadImage') }}</div>
         </template>
       </uploader-action>
     </div>
   </div>
 </template>
 <script>
-  export default {
-    name: 'UploaderInput',
-  };
+export default {
+  name: 'UploaderInput',
+};
 </script>
 <script setup>
-  import { computed, reactive, ref, watch } from 'vue';
-  import { useModal } from '@/sheep/hooks';
-  import { isEmpty, isArray } from 'lodash';
-  import UploaderAction from './uploader-action.vue';
-  import UploaderItem from './uploader-item.vue';
-  import IconPicker from '../sa-icon/sa-icon-picker.vue';
+import { computed, reactive, ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useModal } from '@/sheep/hooks';
+import { isEmpty, isArray } from 'lodash';
+import UploaderAction from './uploader-action.vue';
+import UploaderItem from './uploader-item.vue';
+import IconPicker from '../sa-icon/sa-icon-picker.vue';
 
-  const emit = defineEmits(['updateValue', 'success']);
-  const props = defineProps([
-    'url',
-    'action',
-    'savelog',
-    'group',
-    'fileType',
-    'multiple',
-    'max',
-    'size',
-    'isCropper',
-    'filesize',
-  ]);
+const { t } = useI18n();
 
-  const inputUrl = ref(props.url);
-  watch(
-    () => props.url,
-    () => {
-      inputUrl.value = props.url;
-    },
-  );
+const emit = defineEmits(['updateValue', 'success']);
+const props = defineProps([
+  'url',
+  'action',
+  'savelog',
+  'group',
+  'fileType',
+  'multiple',
+  'max',
+  'size',
+  'isCropper',
+  'filesize',
+]);
 
-  const urlList = reactive({
-    data: computed(() =>
-      !isEmpty(props.url) ? (isArray(props.url) ? props.url : props.url.split(',')) : [],
-    ),
-  });
+const inputUrl = ref(props.url);
+watch(
+  () => props.url,
+  () => {
+    inputUrl.value = props.url;
+  },
+);
 
-  function onUpdateValue(urlList) {
-    emit('updateValue', props.multiple ? urlList : urlList.length > 0 ? urlList[0] : '');
-  }
+const urlList = reactive({
+  data: computed(() =>
+    !isEmpty(props.url) ? (isArray(props.url) ? props.url : props.url.split(',')) : [],
+  ),
+});
 
-  function onSuccess(urlList) {
-    emit('success', urlList);
-  }
+function onUpdateValue(urlList) {
+  emit('updateValue', props.multiple ? urlList : urlList.length > 0 ? urlList[0] : '');
+}
 
-  function onSelect() {
-    useModal(
-      IconPicker,
-      {
-        title: '选择图标',
-      },
-      {
-        confirm: (res) => {
-          emit('updateValue', res.data);
-        },
+function onSuccess(urlList) {
+  emit('success', urlList);
+}
+
+function onSelect() {
+  useModal(
+    IconPicker,
+    {
+      title: t('common.selectIcon'),
+    },
+    {
+      confirm: (res) => {
+        emit('updateValue', res.data);
       },
-    );
-  }
+    },
+  );
+}
 </script>
 <style lang="scss" scoped>
-  .input-item {
-    width: 100%;
-    max-width: 360px;
-    height: 32px;
-    box-sizing: border-box;
-    border: 1px solid var(--sa-border);
-    border-radius: 4px;
-    overflow: hidden;
+.input-item {
+  width: 100%;
+  max-width: 360px;
+  height: 32px;
+  box-sizing: border-box;
+  border: 1px solid var(--sa-border);
+  border-radius: 4px;
+  overflow: hidden;
 
-    :deep() {
-      .el-input {
-        --el-input-border: none;
-        --el-input-border-color: none;
-        line-height: 30px;
+  :deep() {
+    .el-input {
+      --el-input-border: none;
+      --el-input-border-color: none;
+      line-height: 30px;
 
-        .el-input__inner {
-          height: 30px;
-          line-height: 30px;
-          border-radius: 4px 0 0 4px;
-        }
+      .el-input__inner {
+        height: 30px;
+        line-height: 30px;
+        border-radius: 4px 0 0 4px;
       }
     }
+  }
 
-    .url-input {
-      flex: 1;
+  .url-input {
+    flex: 1;
 
-      .el-tooltip__trigger {
-        flex: 1;
-      }
+    .el-tooltip__trigger {
+      flex: 1;
     }
+  }
 
-    .svg {
-      border-left: 1px solid var(--sa-border);
-    }
+  .svg {
+    border-left: 1px solid var(--sa-border);
+  }
 
-    .image {
-      border-left: 1px solid var(--sa-border);
-    }
+  .image {
+    border-left: 1px solid var(--sa-border);
+  }
 
-    .svg,
-    .image {
-      height: 30px;
-      line-height: 30px;
-      padding: 0 12px;
-      font-size: 12px;
-      cursor: pointer;
-    }
+  .svg,
+  .image {
+    height: 30px;
+    line-height: 30px;
+    padding: 0 12px;
+    font-size: 12px;
+    cursor: pointer;
   }
+}
 </style>

+ 199 - 177
src/sheep/layouts/setting.vue

@@ -3,14 +3,14 @@
     <!-- 系统信息 -->
     <div class="setting-item">
       <div class="setting-item__title">
-        系统信息
+        {{ t('modules.setting.systemInfo') }}
         <div class="setting-close">
-          <el-icon class="icons-circle-close"><CircleClose /></el-icon>
-          <el-icon
-            class="icons-circle-close-filled"
-            @click="emit('modalCallBack', { event: 'confirm' })"
-            ><CircleCloseFilled
-          /></el-icon>
+          <el-icon class="icons-circle-close">
+            <CircleClose />
+          </el-icon>
+          <el-icon class="icons-circle-close-filled" @click="emit('modalCallBack', { event: 'confirm' })">
+            <CircleCloseFilled />
+          </el-icon>
           <i></i>
         </div>
       </div>
@@ -24,16 +24,12 @@
     </div>
     <!-- 外观 -->
     <div class="setting-item">
-      <div class="setting-item__title">外观</div>
+      <div class="setting-item__title">{{ t('modules.setting.appearance') }}</div>
       <div class="setting-item__wrap">
-        <div class="title">选择主题</div>
+        <div class="title">{{ t('modules.setting.selectTheme') }}</div>
         <div class="contant sa-flex sa-flex-wrap">
-          <div
-            class="setting-item__theme sa-m-b-32"
-            v-for="item in themeData"
-            :key="item"
-            @click="changeTheme(item.name)"
-          >
+          <div class="setting-item__theme sa-m-b-32" v-for="item in themeData" :key="item"
+            @click="changeTheme(item.name)">
             <div class="theme-image" :class="item.name == appLayout.theme ? 'active' : ''">
               <img v-if="item.name == 'sheep'" src="/static/images/theme/sheep.png" />
               <img v-if="item.name == 'element'" src="/static/images/theme/element.png" />
@@ -44,22 +40,22 @@
             </div>
           </div>
         </div>
-        <div class="title">模式</div>
+        <div class="title">{{ t('modules.setting.mode') }}</div>
         <div class="contant sa-flex">
           <el-radio-group v-model="appLayout.darkMode" @change="changeDarkMode">
-            <el-radio-button label="light">明亮模式</el-radio-button>
-            <el-radio-button label="dark">暗黑模式</el-radio-button>
-            <el-radio-button label="system">跟随系统</el-radio-button>
+            <el-radio-button label="light">{{ t('modules.setting.lightMode') }}</el-radio-button>
+            <el-radio-button label="dark">{{ t('modules.setting.darkMode') }}</el-radio-button>
+            <el-radio-button label="system">{{ t('modules.setting.followSystem') }}</el-radio-button>
           </el-radio-group>
         </div>
       </div>
     </div>
     <!-- 缓存 -->
     <div class="setting-item">
-      <div class="setting-item__title">缓存</div>
+      <div class="setting-item__title">{{ t('modules.setting.cache') }}</div>
       <div class="setting-item__wrap">
         <div class="contant sa-flex sa-flex-wrap">
-          <el-button type="primary" plain @click="clearCache">清除缓存</el-button>
+          <el-button type="primary" plain @click="clearCache">{{ t('modules.setting.clearCache') }}</el-button>
         </div>
       </div>
     </div>
@@ -67,183 +63,209 @@
 </template>
 
 <script setup>
-  import { reactive, ref } from 'vue';
-  import admin from '@/app/admin/api';
-  import { ElMessageBox } from 'element-plus';
-  import { useApp } from '@/sheep/hooks';
-  import { setTheme, setDarkMode } from '@/sheep/hooks/useTheme';
-
-  const { appInfo, appLayout } = useApp();
-
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps(['modal']);
-
-  const themeData = reactive([
-    {
-      name: 'sheep',
-    },
-    {
-      name: 'element',
-    },
-    {
-      name: 'classical',
-    },
-  ]);
-  // 切换主题
-  const changeTheme = (e) => {
-    setTheme(e);
-  };
-  // 切换暗黑模式
-  const changeDarkMode = (e) => {
-    setDarkMode(e);
-  };
-
-  function clearCache() {
-    ElMessageBox.confirm('确定清除缓存?', '提示', {
-      confirmButtonText: '确定',
-      cancelButtonText: '取消',
-      type: 'warning',
-    }).then(async () => {
-      await admin.clearCache();
-    });
-  }
+import { reactive, ref } from 'vue';
+import admin from '@/app/admin/api';
+import { ElMessageBox } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import { useApp } from '@/sheep/hooks';
+import { setTheme, setDarkMode } from '@/sheep/hooks/useTheme';
+
+const { t } = useI18n();
+
+const { appInfo, appLayout } = useApp();
+
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps(['modal']);
+
+const themeData = reactive([
+  {
+    name: 'sheep',
+  },
+  {
+    name: 'element',
+  },
+  {
+    name: 'classical',
+  },
+]);
+// 切换主题
+const changeTheme = (e) => {
+  setTheme(e);
+};
+// 切换暗黑模式
+const changeDarkMode = (e) => {
+  setDarkMode(e);
+};
+
+function clearCache() {
+  ElMessageBox.confirm(t('modules.setting.confirmClearCache'), t('common.tip'), {
+    confirmButtonText: t('common.confirm'),
+    cancelButtonText: t('common.cancel'),
+    type: 'warning',
+  }).then(async () => {
+    await admin.clearCache();
+  });
+}
 </script>
 
 <style lang="scss">
-  .sa-dialog {
-    &.app-setting-dialog {
-      --el-dialog-background-color: transparent !important;
-      background: var(--sa-mask-background);
-      -webkit-backdrop-filter: blur(16px);
-      backdrop-filter: blur(16px);
-      .el-dialog__header {
-        display: none !important;
-      }
+.sa-dialog {
+  &.app-setting-dialog {
+    --el-dialog-background-color: transparent !important;
+    background: var(--sa-mask-background);
+    -webkit-backdrop-filter: blur(16px);
+    backdrop-filter: blur(16px);
 
-      .el-dialog__body {
-        height: 100%;
-        overflow: hidden;
-        overflow-y: auto;
-        display: flex;
-        flex-direction: column;
-        align-items: center;
-        justify-content: space-between;
-        padding: 30px 20px;
-      }
+    .el-dialog__header {
+      display: none !important;
+    }
+
+    .el-dialog__body {
+      height: 100%;
+      overflow: hidden;
+      overflow-y: auto;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: space-between;
+      padding: 30px 20px;
     }
   }
+}
 </style>
 <style lang="scss" scoped>
-  .app-setting {
-    cursor: pointer;
-  }
-  .setting-main {
-    flex: 1;
-    display: flex;
-    align-items: flex-start;
-    justify-content: center;
-    flex-direction: column;
-    min-width: 300px;
-    width: 40%;
-    padding: 64px 0 0 0;
-  }
-  .setting-item {
+.app-setting {
+  cursor: pointer;
+}
+
+.setting-main {
+  flex: 1;
+  display: flex;
+  align-items: flex-start;
+  justify-content: center;
+  flex-direction: column;
+  min-width: 300px;
+  width: 40%;
+  padding: 64px 0 0 0;
+}
+
+.setting-item {
+  width: 100%;
+
+  &__title {
+    margin-bottom: 32px;
+    font-size: 24px;
+    line-height: 24px;
+    font-weight: 800;
+    color: var(--sa-subtitle);
     width: 100%;
-    &__title {
-      margin-bottom: 32px;
-      font-size: 24px;
-      line-height: 24px;
-      font-weight: 800;
-      color: var(--sa-subtitle);
-      width: 100%;
-      position: relative;
-      text-align: left;
-      .setting-close {
-        position: absolute;
-        top: -56px;
-        right: 0;
-        i {
-          font-size: 32px;
-        }
+    position: relative;
+    text-align: left;
+
+    .setting-close {
+      position: absolute;
+      top: -56px;
+      right: 0;
+
+      i {
+        font-size: 32px;
+      }
+
+      .icons-circle-close {
+        display: block;
+      }
+
+      .icons-circle-close-filled {
+        display: none;
+      }
+
+      &:hover {
         .icons-circle-close {
-          display: block;
-        }
-        .icons-circle-close-filled {
           display: none;
         }
-        &:hover {
-          .icons-circle-close {
-            display: none;
-          }
-          .icons-circle-close-filled {
-            display: block;
-            color: var(--el-color-primary);
-          }
+
+        .icons-circle-close-filled {
+          display: block;
+          color: var(--el-color-primary);
         }
       }
     }
-    &__wrap {
-      margin: 0 0 48px;
-      color: var(--sa-font);
-      .name {
-        font-size: 22px;
-        line-height: 22px;
-        font-weight: bold;
-        margin-bottom: 12px;
+  }
+
+  &__wrap {
+    margin: 0 0 48px;
+    color: var(--sa-font);
+
+    .name {
+      font-size: 22px;
+      line-height: 22px;
+      font-weight: bold;
+      margin-bottom: 12px;
+    }
+
+    .version {
+      font-size: 18px;
+      line-height: 18px;
+    }
+
+    .title {
+      font-weight: bold;
+      font-size: 16px;
+      line-height: 16px;
+      margin-bottom: 24px;
+    }
+  }
+
+  &__theme {
+    margin: 0 24px 0 0;
+
+    .theme-image {
+      width: 108px;
+      height: 72px;
+      background: var(--sa-page-background);
+      border: 2px solid var(--sa-border);
+      border-radius: 4px;
+      cursor: pointer;
+      overflow: hidden;
+      position: relative;
+
+      &:hover {
+        border-color: var(--t-btn-hover);
       }
-      .version {
-        font-size: 18px;
-        line-height: 18px;
+
+      &.active {
+        border-color: var(--el-color-primary);
       }
-      .title {
-        font-weight: bold;
-        font-size: 16px;
-        line-height: 16px;
-        margin-bottom: 24px;
+
+      img {
+        width: 100%;
       }
     }
-    &__theme {
-      margin: 0 24px 0 0;
-
-      .theme-image {
-        width: 108px;
-        height: 72px;
-        background: var(--sa-page-background);
-        border: 2px solid var(--sa-border);
-        border-radius: 4px;
-        cursor: pointer;
-        overflow: hidden;
-        position: relative;
-        &:hover {
-          border-color: var(--t-btn-hover);
-        }
-        &.active {
-          border-color: var(--el-color-primary);
-        }
-        img {
-          width: 100%;
-        }
-      }
-      .name {
-        font-size: 16px;
-        line-height: 16px;
-        color: var(--sa-font);
-        margin: 12px 0 0 0;
-      }
+
+    .name {
+      font-size: 16px;
+      line-height: 16px;
+      color: var(--sa-font);
+      margin: 12px 0 0 0;
     }
   }
-  .setting-footer {
-    color: var(--sa-title);
-    &__top {
-      font-size: 12px;
-      margin: 0 0 16px 0;
-      justify-content: space-around;
-      a {
-        color: var(--sa-title) !important;
-      }
-    }
-    &__bottom {
-      font-size: 11px;
+}
+
+.setting-footer {
+  color: var(--sa-title);
+
+  &__top {
+    font-size: 12px;
+    margin: 0 0 16px 0;
+    justify-content: space-around;
+
+    a {
+      color: var(--sa-title) !important;
     }
   }
+
+  &__bottom {
+    font-size: 11px;
+  }
+}
 </style>

+ 383 - 330
src/sheep/layouts/taskbar/index.vue

@@ -2,33 +2,30 @@
   <div class="app-process sa-flex sa-row-between">
     <v-contextmenu ref="cm">
       <v-contextmenu-item :disabled="taskbar.list.length < 2" @click="onClose(selectedCM)">
-        <el-icon><Close /></el-icon>
-        关闭
+        <el-icon>
+          <Close />
+        </el-icon>
+        {{ t('taskbar.close') }}
       </v-contextmenu-item>
       <v-contextmenu-item :disabled="taskbar.list.length < 2" @click="closeOther(selectedCM)">
-        关闭其它
+        {{ t('taskbar.closeOthers') }}
       </v-contextmenu-item>
       <v-contextmenu-divider />
       <v-contextmenu-item>
-        <el-icon><SwitchButton /></el-icon>
-        取消
+        <el-icon>
+          <SwitchButton />
+        </el-icon>
+        {{ t('common.cancel') }}
       </v-contextmenu-item>
     </v-contextmenu>
     <!-- TODO: 页面需要开多个路由栈(传值) -->
     <div class="app-process__scroller sa-flex">
-      <div
-        v-for="(item, index) in taskbar.list"
-        :key="index"
-        class="app-process__item sa-flex"
-        :class="{ active: item.name == taskbar.history[0] }"
-        :data-index="index"
-        @click="onMenu(item)"
-        v-contextmenu:cm
-        @contextmenu.prevent.stop="onCM(e, item)"
-      >
+      <div v-for="(item, index) in taskbar.list" :key="index" class="app-process__item sa-flex"
+        :class="{ active: item.name == taskbar.history[0] }" :data-index="index" @click="onMenu(item)" v-contextmenu:cm
+        @contextmenu.prevent.stop="onCM(e, item)">
         <div class="wrap sa-flex">
           <sa-icon v-if="item.icon" class="sa-m-r-8" :icon="item.icon" size="14" />
-          <span class="text">{{ item.title }}</span>
+          <span class="text">{{ getTaskbarTitle(item) }}</span>
           <div v-if="taskbar.list.length > 1" class="item-close sa-flex sa-row-center">
             <el-icon @click.stop="onClose(item.name)">
               <Close class="close" />
@@ -44,7 +41,9 @@
         <div class="language-trigger">
           <span class="language-icon">🌐</span>
           <span class="language-text">{{ currentLanguageLabel }}</span>
-          <el-icon class="dropdown-icon"><ArrowDown /></el-icon>
+          <el-icon class="dropdown-icon">
+            <ArrowDown />
+          </el-icon>
         </div>
         <template #dropdown>
           <el-dropdown-menu>
@@ -64,27 +63,35 @@
       <el-dropdown @command="handleCommand" trigger="click">
         <div class="user-info sa-flex sa-row-center">
           <el-avatar :size="32" :src="userInfo.avatar" class="user-avatar">
-            {{ (userInfo.name || '暂无信息').charAt(0).toUpperCase() }}
+            {{ (userInfo.name || t('common.noInfo')).charAt(0).toUpperCase() }}
           </el-avatar>
-          <span class="user-name mx-8px">{{ userInfo.name || '暂无信息' }}</span>
+          <span class="user-name mx-8px">{{ userInfo.name || t('common.noInfo') }}</span>
           <el-tag type="info" plain size="small" class="mr-10px">{{
-            userInfo.roleName || '暂无信息'
+            userInfo.roleName || t('common.noInfo')
           }}</el-tag>
-          <el-icon class="dropdown-icon"><ArrowDown /></el-icon>
+          <el-icon class="dropdown-icon">
+            <ArrowDown />
+          </el-icon>
         </div>
         <template #dropdown>
           <el-dropdown-menu>
             <el-dropdown-item command="profile">
-              <el-icon><User /></el-icon>
-              个人中心
+              <el-icon>
+                <User />
+              </el-icon>
+              {{ t('taskbar.profile') }}
             </el-dropdown-item>
             <el-dropdown-item command="changePassword">
-              <el-icon><Lock /></el-icon>
-              修改密码
+              <el-icon>
+                <Lock />
+              </el-icon>
+              {{ t('taskbar.changePassword') }}
             </el-dropdown-item>
             <el-dropdown-item command="logout" divided>
-              <el-icon><SwitchButton /></el-icon>
-              退出登录
+              <el-icon>
+                <SwitchButton />
+              </el-icon>
+              {{ t('taskbar.logout') }}
             </el-dropdown-item>
           </el-dropdown-menu>
         </template>
@@ -94,203 +101,184 @@
 </template>
 
 <script>
-  import { directive } from 'v-contextmenu';
-
-  export default {
-    name: 'taskbar',
-    directives: {
-      contextmenu: directive,
-    },
-  };
+import { directive } from 'v-contextmenu';
+
+export default {
+  name: 'taskbar',
+  directives: {
+    contextmenu: directive,
+  },
+};
 </script>
 <script setup>
-  import { computed, ref } from 'vue';
-  import sheep from '@/sheep';
-  import { useRouter } from 'vue-router';
-  import { closeTaskbar, closeOtherTaskbar } from '@/sheep/hooks/useTaskbar';
-  import { useModal } from '@/sheep/hooks';
-  import { ElMessageBox, ElMessage } from 'element-plus';
-  import { ArrowDown, User, SwitchButton, Lock } from '@element-plus/icons-vue';
-  import PasswordEdit from '@/app/admin/views/auth/admin/password.vue';
-  import { useTranslation } from '@/sheep/utils/i18n';
-
-  const router = useRouter();
-  const selectedCM = ref(0);
-
-  // 任务栏列表
-  const taskbar = computed(() => sheep.$store('app').taskbar);
-
-  // 用户信息
-  const accountStore = sheep.$store('account');
-  const userInfo = computed(() => accountStore.info);
-
-  // 语言切换相关
-  const { locale, changeLanguage, getCurrentLanguageLabel } = useTranslation();
-  const currentLanguageLabel = computed(() => getCurrentLanguageLabel());
-
-  // 点击菜单
-  function onMenu(item) {
-    router.push({
-      path: item.path,
-      query: item.query,
-    });
-  }
-
-  // 右键选项卡
-  function onCM(_, item) {
-    selectedCM.value = item.name;
-  }
-
-  // 关闭
-  function onClose(name) {
-    closeTaskbar(name);
-  }
-
-  // 关闭其他
-  function closeOther(name) {
-    closeOtherTaskbar(name);
-  }
-
-  // 处理下拉菜单命令
-  function handleCommand(command) {
-    switch (command) {
-      case 'profile':
-        router.push('/admin/profile');
-        break;
-      case 'changePassword':
-        changePassword();
-        break;
-      case 'logout':
-        logout();
-        break;
+import { computed, ref } from 'vue';
+import sheep from '@/sheep';
+import { useRouter } from 'vue-router';
+import { closeTaskbar, closeOtherTaskbar } from '@/sheep/hooks/useTaskbar';
+import { useModal } from '@/sheep/hooks';
+import { ElMessageBox, ElMessage } from 'element-plus';
+import { ArrowDown, User, SwitchButton, Lock } from '@element-plus/icons-vue';
+import PasswordEdit from '@/app/admin/views/auth/admin/password.vue';
+import { useTranslation } from '@/sheep/utils/i18n';
+import { useI18n } from 'vue-i18n';
+import { navigationMap } from '@/locales/navigation';
+
+const { t } = useI18n();
+
+const router = useRouter();
+const selectedCM = ref(0);
+
+// 任务栏列表
+const taskbar = computed(() => sheep.$store('app').taskbar);
+
+// 用户信息
+const accountStore = sheep.$store('account');
+const userInfo = computed(() => accountStore.info);
+
+// 语言切换相关
+const { locale, changeLanguage, getCurrentLanguageLabel } = useTranslation();
+const currentLanguageLabel = computed(() => getCurrentLanguageLabel());
+
+// 获取任务栏标题的国际化版本
+function getTaskbarTitle(item) {
+  // 根据路由名称构建 composingKey
+  const composingKey = item.name;
+
+  // 查找对应的翻译键
+  const translationKey = navigationMap[composingKey];
+
+  if (translationKey) {
+    // 使用翻译,如果翻译不存在则使用原标题
+    const translatedText = t(translationKey);
+    if (translatedText !== translationKey) {
+      return translatedText;
     }
   }
 
-  // 处理语言切换
-  function handleLanguageChange(command) {
-    changeLanguage(command);
+  // 如果没有找到翻译键,返回原标题
+  return item.title;
+}
+
+// 监听语言变化,确保taskbar标题能够响应式更新
+// 由于使用了 t() 函数,Vue的响应式系统会自动处理语言切换时的更新
+
+// 点击菜单
+function onMenu(item) {
+  router.push({
+    path: item.path,
+    query: item.query,
+  });
+}
+
+// 右键选项卡
+function onCM(_, item) {
+  selectedCM.value = item.name;
+}
+
+// 关闭
+function onClose(name) {
+  closeTaskbar(name);
+}
+
+// 关闭其他
+function closeOther(name) {
+  closeOtherTaskbar(name);
+}
+
+// 处理下拉菜单命令
+function handleCommand(command) {
+  switch (command) {
+    case 'profile':
+      router.push('/admin/profile');
+      break;
+    case 'changePassword':
+      changePassword();
+      break;
+    case 'logout':
+      logout();
+      break;
   }
-
-  // 修改密码
-  function changePassword() {
-    // 检查用户ID是否为1
-    if (userInfo.value.id == 1) {
-      ElMessage.warning('超级管理员无法修改密码');
-      return;
-    }
-
-    useModal(
-      PasswordEdit,
-      {
-        title: '修改密码',
-        type: 'password',
-        id: userInfo.value.id,
-        name: userInfo.value.name,
-      },
-      {
-        confirm: () => {
-          // 密码修改成功后,调用注销方法退出登录
-          ElMessage.success('密码修改成功,请重新登录');
-          setTimeout(() => {
-            accountStore.logout();
-          }, 500); // 延迟1.5秒后退出登录,让用户看到成功提示
-        },
-      },
-    );
+}
+
+// 处理语言切换
+function handleLanguageChange(command) {
+  changeLanguage(command);
+}
+
+// 修改密码
+function changePassword() {
+  // 检查用户ID是否为1
+  if (userInfo.value.id == 1) {
+    ElMessage.warning(t('taskbar.superAdminCannotChangePassword'));
+    return;
   }
 
-  // 退出登录
-  function logout() {
-    ElMessageBox.confirm('您确定要退出登录吗?', '退出登录', {
-      confirmButtonText: '确定',
-      cancelButtonText: '取消',
-      type: 'warning',
+  useModal(
+    PasswordEdit,
+    {
+      title: t('taskbar.changePassword'),
+      type: 'password',
+      id: userInfo.value.id,
+      name: userInfo.value.name,
+    },
+    {
+      confirm: () => {
+        // 密码修改成功后,调用注销方法退出登录
+        ElMessage.success('密码修改成功,请重新登录');
+        setTimeout(() => {
+          accountStore.logout();
+        }, 500); // 延迟1.5秒后退出登录,让用户看到成功提示
+      },
+    },
+  );
+}
+
+// 退出登录
+function logout() {
+  ElMessageBox.confirm(t('taskbar.confirmLogout'), t('taskbar.logout'), {
+    confirmButtonText: t('common.confirm'),
+    cancelButtonText: t('common.cancel'),
+    type: 'warning',
+  })
+    .then(() => {
+      accountStore.logout();
     })
-      .then(() => {
-        accountStore.logout();
-      })
-      .catch(() => {
-        ElMessage({
-          type: 'info',
-          message: '已取消',
-        });
+    .catch(() => {
+      ElMessage({
+        type: 'info',
+        message: t('common.cancelled'),
       });
-  }
+    });
+}
 </script>
 
 <style lang="scss" scoped>
-  .app-process {
-    display: flex;
-    align-items: center;
-    height: 48px;
-    position: relative;
-    border-bottom: 1px solid var(--sa-border);
-
-    &__scroller {
-      width: 100%;
-      flex: 1;
-      overflow: hidden;
-      padding: 0 16px;
-
-      &::-webkit-scrollbar {
-        display: none;
-      }
-    }
-
-    &__language {
-      flex-shrink: 0;
-      padding-right: 12px;
-
-      .language-switcher {
-        .language-trigger {
-          display: flex;
-          align-items: center;
-          cursor: pointer;
-          padding: 8px 12px;
-          border-radius: 6px;
-          transition: all 0.3s ease;
-
-          &:hover {
-            background-color: var(--el-color-primary-light-9);
-            color: var(--el-color-primary);
-          }
-
-          .language-icon {
-            font-size: 16px;
-            margin-right: 6px;
-          }
-
-          .language-text {
-            font-size: 14px;
-            font-weight: 500;
-            margin-right: 4px;
-          }
-
-          .dropdown-icon {
-            font-size: 12px;
-            color: var(--el-text-color-regular);
-            transition: transform 0.3s ease;
-          }
-
-          &:hover .dropdown-icon {
-            color: var(--el-color-primary);
-          }
-        }
-
-        :deep(.el-dropdown-menu__item) {
-          &.active {
-            color: var(--el-color-primary);
-            background-color: var(--el-color-primary-light-9);
-          }
-        }
-      }
+.app-process {
+  display: flex;
+  align-items: center;
+  height: 48px;
+  position: relative;
+  border-bottom: 1px solid var(--sa-border);
+
+  &__scroller {
+    width: 100%;
+    flex: 1;
+    overflow: hidden;
+    padding: 0 16px;
+
+    &::-webkit-scrollbar {
+      display: none;
     }
+  }
 
-    &__user {
-      flex-shrink: 0;
-      padding-right: 16px;
+  &__language {
+    flex-shrink: 0;
+    padding-right: 12px;
 
-      .user-info {
+    .language-switcher {
+      .language-trigger {
+        display: flex;
+        align-items: center;
         cursor: pointer;
         padding: 8px 12px;
         border-radius: 6px;
@@ -298,27 +286,18 @@
 
         &:hover {
           background-color: var(--el-color-primary-light-9);
+          color: var(--el-color-primary);
         }
 
-        .user-avatar {
-          margin-right: 8px;
-          border: 2px solid var(--el-color-primary-light-7);
-          background-color: var(--el-color-primary);
-          color: white;
-          font-weight: 600;
+        .language-icon {
+          font-size: 16px;
+          margin-right: 6px;
         }
 
-        .user-name {
-          margin-right: 8px;
+        .language-text {
           font-size: 14px;
           font-weight: 500;
-          color: var(--el-text-color-primary);
-        }
-
-        .user-role {
-          margin-right: 8px;
-          background-color: var(--el-color-primary);
-          border-color: var(--el-color-primary);
+          margin-right: 4px;
         }
 
         .dropdown-icon {
@@ -331,138 +310,212 @@
           color: var(--el-color-primary);
         }
       }
+
+      :deep(.el-dropdown-menu__item) {
+        &.active {
+          color: var(--el-color-primary);
+          background-color: var(--el-color-primary-light-9);
+        }
+      }
     }
+  }
 
-    &__item {
-      height: 48px;
-      position: relative;
-      min-width: 38px;
-      transition: all 0.2s;
+  &__user {
+    flex-shrink: 0;
+    padding-right: 16px;
 
-      &::before {
-        content: '';
-        position: absolute;
-        top: 0;
-        left: 50%;
-        margin-left: -12px;
-        height: 4px;
-        border-radius: 2px;
-        background: var(--t-btn-active);
+    .user-info {
+      cursor: pointer;
+      padding: 8px 12px;
+      border-radius: 6px;
+      transition: all 0.3s ease;
+
+      &:hover {
+        background-color: var(--el-color-primary-light-9);
       }
-      &::after {
-        content: '';
-        position: absolute;
-        top: 16px;
-        left: 0;
-        width: 1px;
-        height: 16px;
-        border-radius: 2px;
-        background: var(--sa-border);
+
+      .user-avatar {
+        margin-right: 8px;
+        border: 2px solid var(--el-color-primary-light-7);
+        background-color: var(--el-color-primary);
+        color: white;
+        font-weight: 600;
       }
-      &:first-of-type {
-        &::after {
-          display: none;
-        }
+
+      .user-name {
+        margin-right: 8px;
+        font-size: 14px;
+        font-weight: 500;
+        color: var(--el-text-color-primary);
       }
-      .wrap {
-        min-width: 38px;
-        height: 36px;
-        padding: 0 12px;
-        border-radius: 8px;
+
+      .user-role {
+        margin-right: 8px;
+        background-color: var(--el-color-primary);
+        border-color: var(--el-color-primary);
+      }
+
+      .dropdown-icon {
         font-size: 12px;
-        color: var(--sa-font);
-        display: flex;
-        align-items: center;
-        cursor: pointer;
+        color: var(--el-text-color-regular);
+        transition: transform 0.3s ease;
       }
-      .sa-icon {
-        flex-shrink: 0;
+
+      &:hover .dropdown-icon {
+        color: var(--el-color-primary);
       }
+    }
+  }
 
-      .item-close {
-        flex-shrink: 0;
-        width: 0;
-        height: 16px;
-        overflow: hidden;
-        transition: all 0.3s;
-        .el-icon {
-          font-size: 12px;
-        }
-        &:hover {
-          width: 16px;
-          height: 16px;
-          border-radius: 50%;
-          background: var(--t-bg-active);
-          .close {
-            color: var(--el-color-primary);
-          }
-        }
+  &__item {
+    height: 48px;
+    position: relative;
+    min-width: 38px;
+    transition: all 0.2s;
+
+    &::before {
+      content: '';
+      position: absolute;
+      top: 0;
+      left: 50%;
+      margin-left: -12px;
+      height: 4px;
+      border-radius: 2px;
+      background: var(--t-btn-active);
+    }
+
+    &::after {
+      content: '';
+      position: absolute;
+      top: 16px;
+      left: 0;
+      width: 1px;
+      height: 16px;
+      border-radius: 2px;
+      background: var(--sa-border);
+    }
+
+    &:first-of-type {
+      &::after {
+        display: none;
       }
-      &:not(:hover),
-      &:not(.active) {
-        .text {
-          min-width: 0;
-          overflow: hidden;
-          text-overflow: ellipsis;
-          display: -webkit-box;
-          -webkit-line-clamp: 1;
-          -webkit-box-orient: vertical;
-          height: 23px;
-          line-height: 23px;
-        }
+    }
+
+    .wrap {
+      min-width: 38px;
+      height: 36px;
+      padding: 0 12px;
+      border-radius: 8px;
+      font-size: 12px;
+      color: var(--sa-font);
+      display: flex;
+      align-items: center;
+      cursor: pointer;
+    }
+
+    .sa-icon {
+      flex-shrink: 0;
+    }
+
+    .item-close {
+      flex-shrink: 0;
+      width: 0;
+      height: 16px;
+      overflow: hidden;
+      transition: all 0.3s;
+
+      .el-icon {
+        font-size: 12px;
       }
 
       &:hover {
-        flex-shrink: 0;
-        min-width: unset;
-        .wrap {
+        width: 16px;
+        height: 16px;
+        border-radius: 50%;
+        background: var(--t-bg-active);
+
+        .close {
           color: var(--el-color-primary);
-          background: var(--t-bg-hover);
         }
+      }
+    }
 
-        &::before {
-          width: 24px;
-        }
-        .item-close {
-          width: 16px;
-          margin-left: 8px;
-        }
+    &:not(:hover),
+    &:not(.active) {
+      .text {
+        min-width: 0;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        display: -webkit-box;
+        -webkit-line-clamp: 1;
+        -webkit-box-orient: vertical;
+        height: 23px;
+        line-height: 23px;
+      }
+    }
+
+    &:hover {
+      flex-shrink: 0;
+      min-width: unset;
+
+      .wrap {
+        color: var(--el-color-primary);
+        background: var(--t-bg-hover);
+      }
+
+      &::before {
+        width: 24px;
+      }
+
+      .item-close {
+        width: 16px;
+        margin-left: 8px;
+      }
+
+      &:after {
+        display: none;
+      }
+
+      &+.app-process__item {
         &:after {
           display: none;
         }
-        & + .app-process__item {
-          &:after {
-            display: none;
-          }
-        }
       }
-      &.active {
-        flex-shrink: 0;
-        min-width: unset;
-        .wrap {
-          color: var(--el-color-primary);
-          background: var(--t-bg-hover);
-        }
-        &::before {
-          width: 24px;
-          background-color: var(--el-color-primary);
-        }
-        .item-close {
-          width: 16px;
-          margin-left: 8px;
-        }
+    }
+
+    &.active {
+      flex-shrink: 0;
+      min-width: unset;
+
+      .wrap {
+        color: var(--el-color-primary);
+        background: var(--t-bg-hover);
+      }
+
+      &::before {
+        width: 24px;
+        background-color: var(--el-color-primary);
+      }
+
+      .item-close {
+        width: 16px;
+        margin-left: 8px;
+      }
+
+      &:after {
+        display: none;
+      }
+
+      &+.app-process__item {
         &:after {
           display: none;
         }
-        & + .app-process__item {
-          &:after {
-            display: none;
-          }
-        }
       }
     }
-    @media only screen and (max-width: 768px) {
-      display: none;
-    }
   }
+
+  @media only screen and (max-width: 768px) {
+    display: none;
+  }
+}
 </style>

+ 235 - 265
src/sheep/views/login/index.vue

@@ -1,8 +1,5 @@
 <template>
-  <div
-    class="login-content"
-    :style="{ 'background-image': 'url(' + login.config.background + ')' }"
-  >
+  <div class="login-content" :style="{ 'background-image': 'url(' + login.config.background + ')' }">
     <el-row>
       <el-col :xs="0" :sm="0" :md="13" :lg="14" :xl="14"></el-col>
       <el-col class="main" :xs="24" :sm="24" :md="11" :lg="10" :xl="10">
@@ -15,13 +12,7 @@
                 <div class="admin-welcome">{{ t('modules.auth.niceToSeeYou') }}</div>
               </div>
             </div>
-            <el-form
-              :model="form.data"
-              :rules="form.rules"
-              label-position="left"
-              ref="formRef"
-              label-width="0"
-            >
+            <el-form :model="form.data" :rules="form.rules" label-position="left" ref="formRef" label-width="0">
               <el-form-item prop="account">
                 <el-input :class="form.data.account ? 'is-focus' : ''" v-model="form.data.account">
                   <template #prefix>
@@ -30,12 +21,8 @@
                 </el-input>
               </el-form-item>
               <el-form-item prop="pwd">
-                <el-input
-                  :class="form.data.pwd ? 'is-focus' : ''"
-                  v-model="form.data.pwd"
-                  :type="showPwd ? 'password' : 'text'"
-                  autocomplete="new-password"
-                >
+                <el-input :class="form.data.pwd ? 'is-focus' : ''" v-model="form.data.pwd"
+                  :type="showPwd ? 'password' : 'text'" autocomplete="new-password">
                   <template #prefix>
                     <div class="label">{{ t('modules.auth.password') }}</div>
                   </template>
@@ -51,11 +38,8 @@
               </el-form-item>
 
               <div>
-                <el-checkbox
-                  v-model="login.last.rememberMe"
-                  @change="changeRememberMe"
-                  :label="t('modules.auth.rememberMe')"
-                ></el-checkbox>
+                <el-checkbox v-model="login.last.rememberMe" @change="changeRememberMe"
+                  :label="t('modules.auth.rememberMe')"></el-checkbox>
               </div>
               <el-button type="primary" :loading="loginLoading" @click="accountLogin">
                 {{ t('modules.auth.login') }}
@@ -63,32 +47,19 @@
             </el-form>
           </div>
           <!-- 有记住的用户 -->
-          <div
-            v-else-if="login.type == 'rememberMe'"
-            class="login-wrap sa-flex sa-flex-center sa-flex-1"
-          >
+          <div v-else-if="login.type == 'rememberMe'" class="login-wrap sa-flex sa-flex-center sa-flex-1">
             <div class="admin-name sa-flex sa-row-center">
               <sa-image :url="login.last.info.avatar" size="80" radius="40"></sa-image>
             </div>
-            <el-form
-              :model="form.data"
-              :rules="form.rules"
-              label-position="left"
-              ref="formRef"
-              label-width="0"
-            >
+            <el-form :model="form.data" :rules="form.rules" label-position="left" ref="formRef" label-width="0">
               <el-form-item prop="account">
                 <div class="sa-flex-1 sa-flex sa-row-center">
                   {{ login.last.info.nickname }}
                 </div>
               </el-form-item>
               <el-form-item prop="pwd">
-                <el-input
-                  :class="form.data.pwd ? 'is-focus' : ''"
-                  v-model="form.data.pwd"
-                  :type="showPwd ? 'password' : 'text'"
-                  autocomplete="new-password"
-                >
+                <el-input :class="form.data.pwd ? 'is-focus' : ''" v-model="form.data.pwd"
+                  :type="showPwd ? 'password' : 'text'" autocomplete="new-password">
                   <template #prefix>
                     <div class="label">{{ t('modules.auth.password') }}</div>
                   </template>
@@ -103,8 +74,7 @@
                 </el-input>
               </el-form-item>
 
-              <el-button type="primary" :loading="loginLoading" @click="accountLogin"
-                >登录
+              <el-button type="primary" :loading="loginLoading" @click="accountLogin">登录
               </el-button>
               <el-button @click="changeLoginType('input')"> 使用其他账号登录 </el-button>
             </el-form>
@@ -116,272 +86,259 @@
 </template>
 
 <script setup>
-  import { reactive, ref, unref, computed, onBeforeMount, onMounted, onUnmounted } from 'vue';
-
-  import router from '@/sheep/router';
-  import { useRoute } from 'vue-router';
-  import adminApi from '@/sheep/local-data/admin';
-  import storage from '@/sheep/utils/storage';
-  import sheep from '@/sheep';
-  import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
-  import { ElMessage } from 'element-plus';
-  import { useI18n } from 'vue-i18n';
-
-  const { t } = useI18n();
-
-  const accountStore = sheep.$store('account');
-  const appStore = sheep.$store('app');
-  const routeQuery = useRoute().query;
-  if (routeQuery.token) {
-    accountStore.setToken(`${routeQuery.token}`);
-  }
-  const appName = computed(() => appStore.info.name);
-
-  const showPwd = ref(true);
-
-  const loginLoading = ref(false);
-  const formRef = ref(null);
-
-  const form = reactive({
-    data: {
-      account: '',
-      pwd: '',
-    },
-    get rules() {
-      return {
-        account: [{ required: true, message: t('modules.auth.accountRequired'), trigger: 'blur' }],
-        pwd: [{ required: true, message: t('modules.auth.passwordRequired'), trigger: 'blur' }],
-      };
-    },
-  });
-
-  // 登录
-  const accountLogin = async () => {
-    // 表单验证
-    unref(formRef).validate(async (valid) => {
-      if (!valid) return;
-      loginLoading.value = true;
-      try {
-        let submit = form.data;
-        const { code } = await accountStore.login(submit);
-        if (code == '200') {
-          ElMessage.success(t('modules.auth.loginSuccess'));
-          storage.set('lastLogin', login.last);
-          await appStore.appLoad();
-          router.push('/');
-        }
-      } catch (error) {
-        console.error('登录失败:', error);
-        // 错误信息通常由 store 或 request 拦截器处理,这里只记录日志
-      } finally {
-        loginLoading.value = false;
+import { reactive, ref, unref, computed, onBeforeMount, onMounted, onUnmounted } from 'vue';
+
+import router from '@/sheep/router';
+import { useRoute } from 'vue-router';
+import adminApi from '@/sheep/local-data/admin';
+import storage from '@/sheep/utils/storage';
+import sheep from '@/sheep';
+import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
+
+const accountStore = sheep.$store('account');
+const appStore = sheep.$store('app');
+const routeQuery = useRoute().query;
+if (routeQuery.token) {
+  accountStore.setToken(`${routeQuery.token}`);
+}
+const appName = computed(() => appStore.info.name);
+
+const showPwd = ref(true);
+
+const loginLoading = ref(false);
+const formRef = ref(null);
+
+const form = reactive({
+  data: {
+    account: '',
+    pwd: '',
+  },
+  get rules() {
+    return {
+      account: [{ required: true, message: t('modules.auth.accountRequired'), trigger: 'blur' }],
+      pwd: [{ required: true, message: t('modules.auth.passwordRequired'), trigger: 'blur' }],
+    };
+  },
+});
+
+// 登录
+const accountLogin = async () => {
+  // 表单验证
+  unref(formRef).validate(async (valid) => {
+    if (!valid) return;
+    loginLoading.value = true;
+    try {
+      let submit = form.data;
+      const { code } = await accountStore.login(submit);
+      if (code == '200') {
+        ElMessage.success(t('modules.auth.loginSuccess'));
+        storage.set('lastLogin', login.last);
+        await appStore.appLoad();
+        router.push('/');
       }
-    });
-  };
-
-  const login = reactive({
-    config: {
-      background: '',
-    },
-    type: 'input',
-    last: {},
-  });
-
-  // 初始化登陆表单
-  function checkLastLogin() {
-    login.last = storage.get('lastLogin', { rememberMe: false });
-    if (login.last.rememberMe) {
-      login.type = 'rememberMe';
-      form.data.account = login.last.info.account;
+    } catch (error) {
+      console.error('登录失败:', error);
+      // 错误信息通常由 store 或 request 拦截器处理,这里只记录日志
+    } finally {
+      loginLoading.value = false;
     }
+  });
+};
+
+const login = reactive({
+  config: {
+    background: '',
+  },
+  type: 'input',
+  last: {},
+});
+
+// 初始化登陆表单
+function checkLastLogin() {
+  login.last = storage.get('lastLogin', { rememberMe: false });
+  if (login.last.rememberMe) {
+    login.type = 'rememberMe';
+    form.data.account = login.last.info.account;
   }
-
-  function changeLoginType(type) {
-    login.type = type;
-    if (type == 'input') {
-      form.data.account = '';
-      form.data.pwd = '';
-      initLogin();
-    }
+}
+
+function changeLoginType(type) {
+  login.type = type;
+  if (type == 'input') {
+    form.data.account = '';
+    form.data.pwd = '';
+    initLogin();
   }
-
-  function changeRememberMe(e) {
-    login.last.rememberMe = e;
+}
+
+function changeRememberMe(e) {
+  login.last.rememberMe = e;
+}
+
+onBeforeMount(async () => {
+  // 检测登录态
+  if (accountStore.isLogin) {
+    await appStore.appLoad();
+    router.push('/');
+  } else {
+    initLogin();
+    checkLastLogin();
   }
+});
 
-  onBeforeMount(async () => {
-    // 检测登录态
-    if (accountStore.isLogin) {
-      await appStore.appLoad();
-      router.push('/');
-    } else {
-      initLogin();
-      checkLastLogin();
-    }
-  });
-
-  onMounted(() => {
-    document.addEventListener('keyup', enterKey);
-  });
+onMounted(() => {
+  document.addEventListener('keyup', enterKey);
+});
 
-  onUnmounted(() => {
-    document.removeEventListener('keyup', enterKey);
-  });
+onUnmounted(() => {
+  document.removeEventListener('keyup', enterKey);
+});
 
-  function enterKey(event) {
-    const code = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
-    if (code == 13) {
-      accountLogin();
-    }
+function enterKey(event) {
+  const code = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
+  if (code == 13) {
+    accountLogin();
   }
+}
 
-  async function initLogin() {
-    const { data } = adminApi.loginConfig();
-    login.config = data;
-    login.config.background = login.config.background;
-  }
+async function initLogin() {
+  const { data } = adminApi.loginConfig();
+  login.config = data;
+  login.config.background = login.config.background;
+}
 </script>
 <style lang="scss">
-  .login-content {
-    .el-form {
-      width: 100%;
+.login-content {
+  .el-form {
+    width: 100%;
 
-      .el-button {
-        width: 100%;
-        height: 48px;
-        line-height: 1;
-        margin-top: 10px;
-      }
+    .el-button {
+      width: 100%;
+      height: 48px;
+      line-height: 1;
+      margin-top: 10px;
+    }
 
-      .el-button--default {
-        margin-top: 0;
+    .el-button--default {
+      margin-top: 0;
 
-        .sa-svg {
-          font-size: 22px;
-          margin-right: 6px;
-        }
+      .sa-svg {
+        font-size: 22px;
+        margin-right: 6px;
       }
+    }
 
-      .el-form-item {
-        margin-bottom: 32px;
-      }
+    .el-form-item {
+      margin-bottom: 32px;
+    }
 
-      .el-form-item__content {
-        line-height: unset;
-      }
+    .el-form-item__content {
+      line-height: unset;
     }
   }
+}
 </style>
 <style lang="scss" scoped>
-  .login-content {
+.login-content {
+  height: 100%;
+  color: var(--sa-subtitle);
+  background-size: cover;
+  background-repeat: no-repeat;
+  background-color: var(--sa-background-assist);
+
+  @media only screen and (max-width: 768px) {
+    background-position: center;
+  }
+
+  .el-row {
     height: 100%;
-    color: var(--sa-subtitle);
-    background-size: cover;
-    background-repeat: no-repeat;
-    background-color: var(--sa-background-assist);
 
-    @media only screen and (max-width: 768px) {
-      background-position: center;
+    .el-col {
+      display: flex;
+      align-items: center;
     }
 
-    .el-row {
-      height: 100%;
-
+    @media only screen and (max-width: 992px) {
       .el-col {
-        display: flex;
-        align-items: center;
-      }
-
-      @media only screen and (max-width: 992px) {
-        .el-col {
-          justify-content: center;
-        }
+        justify-content: center;
       }
     }
+  }
 
-    .main {
-      position: relative;
-    }
+  .main {
+    position: relative;
+  }
 
-    .login-wrap {
-      width: 460px;
-      flex-direction: column;
-      padding: 56px 52px 64px;
-      border-radius: 8px;
-      background: var(--sa-background-assist);
-      filter: drop-shadow(0 0 16px rgba(0, 0, 0, 0.2));
-      position: absolute;
+  .login-wrap {
+    width: 460px;
+    flex-direction: column;
+    padding: 56px 52px 64px;
+    border-radius: 8px;
+    background: var(--sa-background-assist);
+    filter: drop-shadow(0 0 16px rgba(0, 0, 0, 0.2));
+    position: absolute;
 
-      .admin-name {
-        width: 100%;
-        height: 112px;
-        font-size: 32px;
-        text-align: center;
+    .admin-name {
+      width: 100%;
+      height: 112px;
+      font-size: 28px;
+      text-align: center;
 
-        .admin-welcome {
-          font-size: 20px;
-          margin-top: 16px;
-          text-align: center;
-        }
+      .admin-welcome {
+        font-size: 20px;
+        margin-top: 16px;
+        text-align: center;
+      }
 
-        .sa-image {
-          margin-bottom: 32px;
-        }
+      .sa-image {
+        margin-bottom: 32px;
       }
     }
+  }
 
+  .login-wrap {
+    height: 500px;
+  }
+
+  @media only screen and (max-width: 768px) {
     .login-wrap {
-      height: 500px;
-    }
+      width: 100%;
+      max-width: unset;
+      height: 100%;
+      border-radius: 0;
+      padding: 56px 20px 64px;
+      background: transparent;
+      filter: unset;
 
-    @media only screen and (max-width: 768px) {
-      .login-wrap {
-        width: 100%;
-        max-width: unset;
-        height: 100%;
-        border-radius: 0;
-        padding: 56px 20px 64px;
-        background: transparent;
-        filter: unset;
-
-        .admin-name {
-          font-size: 26px;
-        }
+      .admin-name {
+        font-size: 26px;
       }
     }
+  }
 
-    :deep() {
-      .el-input {
-        --el-input-height: 48px;
-        max-width: unset;
-
-        .el-input__wrapper {
-          position: relative;
-
-          .label {
-            display: flex;
-            align-items: center;
-            width: fit-content;
-            height: 20px;
-            position: absolute;
-            top: 14px;
-            left: 11px;
-            pointer-events: none;
-            font-size: 18px;
-            color: var(--sa-subfont);
-          }
-
-          &.is-focus {
-            .label {
-              top: 4px;
-              font-size: 12px;
-              transition: ease-in-out 0.2s;
-            }
-
-            .el-input__inner {
-              padding: 25px 0 9px;
-            }
-          }
+  :deep() {
+    .el-input {
+      --el-input-height: 48px;
+      max-width: unset;
+
+      .el-input__wrapper {
+        position: relative;
+
+        .label {
+          display: flex;
+          align-items: center;
+          width: fit-content;
+          height: 20px;
+          position: absolute;
+          top: 14px;
+          left: 11px;
+          pointer-events: none;
+          font-size: 18px;
+          color: var(--sa-subfont);
         }
 
         &.is-focus {
@@ -396,6 +353,19 @@
           }
         }
       }
+
+      &.is-focus {
+        .label {
+          top: 4px;
+          font-size: 12px;
+          transition: ease-in-out 0.2s;
+        }
+
+        .el-input__inner {
+          padding: 25px 0 9px;
+        }
+      }
     }
   }
+}
 </style>