PUGE 3 semanas atrás
pai
commit
f41ed72c4d

+ 1 - 0
src/components/custom/strength-meter.vue

@@ -65,6 +65,7 @@ const watchStrength = (password: string): number => {
       />
     </AFormItem>
   </ACol>
+
   <!-- 显示密码强度 -->
   <div class="process-steps">
     <AProgress

+ 31 - 10
src/hooks/common/table.ts

@@ -13,11 +13,20 @@ type TableData = AntDesign.TableData;
 type GetTableData<A extends AntDesign.TableApiFn> = AntDesign.GetTableData<A>;
 type TableColumn<T> = AntDesign.TableColumn<T>;
 
-export function useTable<A extends AntDesign.TableApiFn>(config: AntDesign.AntDesignTableConfig<A>) {
+// 扩展配置类型,添加信息回调
+export interface UseTableConfig<A extends AntDesign.TableApiFn> extends AntDesign.AntDesignTableConfig<A> {
+  // 添加信息回调
+  onInfoUpdate?: (info: any[]) => void;
+}
+
+export function useTable<A extends AntDesign.TableApiFn>(config: UseTableConfig<A>) {
   const scope = effectScope();
   const appStore = useAppStore();
 
-  const { apiFn, apiParams, immediate } = config;
+  const { apiFn, apiParams, immediate, onInfoUpdate } = config;
+
+  // 创建信息的响应式引用
+  const info = ref<any[]>([]);
 
   const {
     loading,
@@ -34,8 +43,8 @@ export function useTable<A extends AntDesign.TableApiFn>(config: AntDesign.AntDe
     apiFn,
     apiParams,
     columns: config.columns,
-    transformer: res => {
-      const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
+    transformer: (res) => {
+      const { records = [], current = 1, size = 10, total = 0, info: responseInfo = [] } = res.data || {};
 
       const recordsWithIndex = records.map((item, index) => {
         return {
@@ -44,17 +53,28 @@ export function useTable<A extends AntDesign.TableApiFn>(config: AntDesign.AntDe
         };
       });
 
+      // 存储信息数据
+      if (Array.isArray(responseInfo)) {
+        info.value = responseInfo;
+
+        // 如果有回调函数,调用它
+        if (onInfoUpdate) {
+          onInfoUpdate(responseInfo);
+        }
+      }
+
       return {
         data: recordsWithIndex,
         pageNum: current,
         pageSize: size,
         total,
+        info: responseInfo, // 传递信息数据
       };
     },
-    getColumnChecks: cols => {
+    getColumnChecks: (cols) => {
       const checks: AntDesign.TableColumnCheck[] = [];
 
-      cols.forEach(column => {
+      cols.forEach((column) => {
         if (column.key) {
           checks.push({
             key: column.key as string,
@@ -69,19 +89,19 @@ export function useTable<A extends AntDesign.TableApiFn>(config: AntDesign.AntDe
     getColumns: (cols, checks) => {
       const columnMap = new Map<string, TableColumn<GetTableData<A>>>();
 
-      cols.forEach(column => {
+      cols.forEach((column) => {
         if (column.key) {
           columnMap.set(column.key as string, column);
         }
       });
 
       const filteredColumns = checks
-        .filter(item => item.checked)
-        .map(check => columnMap.get(check.key) as TableColumn<GetTableData<A>>);
+        .filter((item) => item.checked)
+        .map((check) => columnMap.get(check.key) as TableColumn<GetTableData<A>>);
 
       return filteredColumns;
     },
-    onFetched: async transformed => {
+    onFetched: async (transformed) => {
       const { pageNum, pageSize, total } = transformed;
 
       updatePagination({
@@ -171,6 +191,7 @@ export function useTable<A extends AntDesign.TableApiFn>(config: AntDesign.AntDe
     searchParams,
     updateSearchParams,
     resetSearchParams,
+    info, // 返回信息数据
   };
 }
 

+ 5 - 1
src/typings/antd.d.ts

@@ -33,8 +33,12 @@ declare namespace AntDesign {
 
   type GetTableData<A extends TableApiFn> = A extends TableApiFn<infer T> ? T : never;
 
+  // 扩展 AntDesignTableConfig 类型,添加统计信息支持
   type AntDesignTableConfig<A extends TableApiFn> = Pick<
     import('@sa/hooks').TableConfig<A, GetTableData<A>, TableColumn<TableDataWithIndex<GetTableData<A>>>>,
     'apiFn' | 'apiParams' | 'columns' | 'immediate'
-  >;
+  > & {
+    // 添加可选的统计信息回调
+    onStatsUpdate?: (stats: any) => void;
+  };
 }

+ 14 - 5
src/typings/api.d.ts

@@ -17,8 +17,8 @@ declare namespace Api {
 
     /** @description: 查询时间 */
     type SearchTime = {
-      startTime?: number; // 开始时间
-      endTime?: number; // 结束时间
+      startDate?: string; // 开始时间
+      endDate?: string; // 结束时间
     };
 
     /** @description: 公共字段 */
@@ -238,11 +238,20 @@ declare namespace Api {
 
     /** @description: 查询参数 */
     type ActivationCodeSearchParams = Partial<
-      Pick<ActivationCode, 'code' | 'type' | 'activated' | 'refunded' | 'revoked'>
+      Pick<ActivationCode, 'code' | 'type' | 'activated' | 'refunded' | 'revoked' | 'startDate' | 'endDate'>
     > & {
       typeId?: string; // 新的类型ID筛选
-      startDate?: string; // 开始日期
-      endDate?: string; // 结束日期
+
+      // 状态筛选
+      activated?: number; // 激活状态:0-未使用,1-已激活,2-已过期,3-已禁用
+
+      // 时间范围筛选
+      startDate?: string; // 创建时间开始
+      endTime?: string; // 创建时间结束
+
+      // 注意:这里的字段名要和组件中的使用保持一致
+      // 如果你在组件中使用了 startDate 和 endDate,那么就保持 startDate 和 endDate
+      // 但在我的组件代码中,我使用的是 startDate 和 endTime
     } & Api.Common.PaginatingParams;
 
     /** @description: 创建/更新激活码 */

+ 261 - 3
src/views/activation-code/index.vue

@@ -1,7 +1,6 @@
 <script setup lang="tsx">
 import { Button, Popconfirm, Tag, Tooltip } from 'ant-design-vue';
 import dayjs from 'dayjs';
-import { onMounted, ref } from 'vue';
 
 import SvgIcon from '@/components/custom/svg-icon.vue';
 import { UNIFORM_TEXT } from '@/enum';
@@ -22,7 +21,87 @@ import ExportModal from './modules/export-modal.vue';
 import HeaderSearch from './modules/header-search.vue';
 import ImportDrawer from './modules/import-drawer.vue';
 import RefundModal from './modules/refund-modal.vue';
+import { computed, onMounted, ref } from 'vue';
+
+const expandStats = ref(false);
+
+// 计算统计信息
+const stats = computed(() => {
+  if (!tableInfo.value || !Array.isArray(tableInfo.value)) {
+    return {
+      totalCount: 0,
+      activeCount: 0,
+      refundCount: 0,
+      revokedCount: 0,
+      inactiveCount: 0,
+      // 按类型统计
+      typeStats: {},
+      // 按类型+激活状态统计
+      detailedStats: {},
+    };
+  }
+
+  const info = tableInfo.value;
+  const typeStats: Record<string, number> = {};
+  const detailedStats: Record<string, {
+    total: number;
+    active: number;
+    refunded: number;
+    revoked: number;
+    inactive: number;
+  }> = {};
+
+  // 遍历 info 数组进行统计
+  info.forEach((item: any) => {
+    const typeId = item.typeId || 'unknown';
+    const typeName = activationCodeTypeOptions.value.find(opt => opt.id === typeId)?.label || `类型${typeId}`;
+
+    // 统计总数
+    typeStats[typeName] = (typeStats[typeName] || 0) + 1;
+
+    // 初始化详细统计
+    if (!detailedStats[typeName]) {
+      detailedStats[typeName] = {
+        total: 0,
+        active: 0,
+        refunded: 0,
+        revoked: 0,
+        inactive: 0,
+      };
+    }
 
+    // 更新详细统计
+    detailedStats[typeName].total += 1;
+    if (item.activated) {
+      detailedStats[typeName].active += 1;
+    } else {
+      detailedStats[typeName].inactive += 1;
+    }
+    if (item.refunded) {
+      detailedStats[typeName].refunded += 1;
+    }
+    if (item.revoked) {
+      detailedStats[typeName].revoked += 1;
+    }
+  });
+
+  // 计算总体统计
+  const totalCount = info.length;
+  const activeCount = info.filter(item => item.activated).length;
+  const refundCount = info.filter(item => item.refunded).length;
+  const revokedCount = info.filter(item => item.revoked).length;
+  const inactiveCount = info.filter(item => !item.activated).length;
+
+  return {
+    totalCount,
+    activeCount,
+    refundCount,
+    revokedCount,
+    inactiveCount,
+    typeStats,
+    detailedStats,
+  };
+});
 const { userInfo } = useAuthStore();
 
 // 激活码类型选项
@@ -107,6 +186,7 @@ const {
   searchParams,
   updateSearchParams,
   resetSearchParams,
+  info: tableInfo, // 改为 info 字段
 } = useTable({
   apiFn: getActivationCodeList,
   apiParams: {
@@ -276,6 +356,10 @@ const {
       ),
     },
   ],
+  // 可选:通过回调接收统计信息
+  onStatsUpdate: (newStats) => {
+    console.log('统计信息更新:', newStats);
+  },
 });
 
 const { checkedRowKeys, onDeleted } = useTableOperate(data, getData);
@@ -475,11 +559,143 @@ onMounted(() => {
 
     <!-- 退款弹窗 -->
     <RefundModal v-model:visible="refundModalVisible" :record="refundingRecord" @confirm="confirmRefund" />
+    <!-- 统计信息显示 - 精简模式 -->
+    <div v-if="stats.totalCount > 0" class="stats-container mt-4 p-3 bg-blue-50 rounded-lg border border-blue-200" style="position: absolute;bottom: 10px;">
+      <!-- 第一行:基础统计 + 展开按钮 -->
+      <div class="flex items-center justify-between">
+        <div class="flex items-center flex-wrap gap-3">
+          <span class="text-blue-700 font-medium">搜索结果统计:</span>
+
+          <span class="px-2 py-1 bg-white rounded shadow-sm border">
+            总数 <strong class="ml-1 text-lg">{{ stats.totalCount }}</strong>
+          </span>
+
+          <span class="px-2 py-1 bg-green-100 text-green-800 rounded shadow-sm">
+            已激活 <strong class="ml-1">{{ stats.activeCount }}</strong>
+          </span>
+
+          <span class="px-2 py-1 bg-yellow-100 text-yellow-800 rounded shadow-sm">
+            已退款 <strong class="ml-1">{{ stats.refundCount }}</strong>
+          </span>
+
+          <span class="px-2 py-1 bg-red-100 text-red-800 rounded shadow-sm">
+            已收回 <strong class="ml-1">{{ stats.revokedCount }}</strong>
+          </span>
+
+          <span class="px-2 py-1 bg-gray-100 text-gray-800 rounded shadow-sm">
+            未激活 <strong class="ml-1">{{ stats.inactiveCount }}</strong>
+          </span>
+        </div>
+
+        <!-- 展开/收起按钮 -->
+        <Button
+          type="link"
+          size="small"
+          class="flex items-center text-blue-600 hover:text-blue-800"
+          @click="expandStats = !expandStats"
+        >
+          <template #icon>
+            <SvgIcon
+              :icon="expandStats ? 'carbon:chevron-up' : 'carbon:chevron-down'"
+              class="mr-1"
+            />
+          </template>
+          {{ expandStats ? '收起详情' : '展开详情' }}
+        </Button>
+      </div>
+
+      <!-- 展开的详细统计信息 -->
+      <div v-if="expandStats" class="mt-4 pt-4 border-t border-blue-300">
+        <!-- 按类型统计 -->
+        <div v-if="Object.keys(stats.typeStats).length > 0" class="mb-4">
+          <h4 class="text-blue-700 font-medium mb-3">按类型统计:</h4>
+          <div class="flex flex-wrap gap-3">
+            <div
+              v-for="(count, typeName) in stats.typeStats"
+              :key="typeName"
+              class="px-3 py-2 bg-blue-100 text-blue-800 rounded shadow-sm border border-blue-300 flex items-center"
+            >
+              <div class="w-2 h-2 bg-blue-500 rounded-full mr-2"></div>
+              {{ typeName }} <strong class="ml-2 text-lg">{{ count }}</strong>
+            </div>
+          </div>
+        </div>
+
+        <!-- 详细统计表格 -->
+        <div v-if="Object.keys(stats.detailedStats).length > 0">
+          <h4 class="text-blue-700 font-medium mb-3">详细统计:</h4>
+          <div class="overflow-x-auto rounded-lg border border-blue-200 bg-white">
+            <table class="min-w-full">
+              <thead>
+                <tr class="bg-blue-50">
+                  <th class="px-4 py-3 border-b text-left font-medium text-blue-700">类型</th>
+                  <th class="px-4 py-3 border-b text-center font-medium text-blue-700">总数</th>
+                  <th class="px-4 py-3 border-b text-center font-medium text-blue-700">已激活</th>
+                  <th class="px-4 py-3 border-b text-center font-medium text-blue-700">未激活</th>
+                  <th class="px-4 py-3 border-b text-center font-medium text-blue-700">已退款</th>
+                  <th class="px-4 py-3 border-b text-center font-medium text-blue-700">已收回</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr
+                  v-for="(typeStats, typeName) in stats.detailedStats"
+                  :key="typeName"
+                  class="hover:bg-blue-50 transition-colors"
+                >
+                  <td class="px-4 py-3 border-b">
+                    <div class="flex items-center">
+                      <div class="w-3 h-3 bg-blue-500 rounded mr-2"></div>
+                      <span class="font-medium">{{ typeName }}</span>
+                    </div>
+                  </td>
+                  <td class="px-4 py-3 border-b text-center font-bold">{{ typeStats.total }}</td>
+                  <td class="px-4 py-3 border-b text-center">
+                    <span v-if="typeStats.active > 0" class="px-2 py-1 bg-green-100 text-green-700 rounded text-sm">
+                      {{ typeStats.active }}
+                    </span>
+                    <span v-else class="text-gray-400">0</span>
+                  </td>
+                  <td class="px-4 py-3 border-b text-center">
+                    <span v-if="typeStats.inactive > 0" class="px-2 py-1 bg-gray-100 text-gray-700 rounded text-sm">
+                      {{ typeStats.inactive }}
+                    </span>
+                    <span v-else class="text-gray-400">0</span>
+                  </td>
+                  <td class="px-4 py-3 border-b text-center">
+                    <span v-if="typeStats.refunded > 0" class="px-2 py-1 bg-yellow-100 text-yellow-700 rounded text-sm">
+                      {{ typeStats.refunded }}
+                    </span>
+                    <span v-else class="text-gray-400">0</span>
+                  </td>
+                  <td class="px-4 py-3 border-b text-center">
+                    <span v-if="typeStats.revoked > 0" class="px-2 py-1 bg-red-100 text-red-700 rounded text-sm">
+                      {{ typeStats.revoked }}
+                    </span>
+                    <span v-else class="text-gray-400">0</span>
+                  </td>
+                </tr>
+              </tbody>
+              <!-- 总计行 -->
+              <tfoot>
+                <tr class="bg-blue-50 font-bold">
+                  <td class="px-4 py-3">总计</td>
+                  <td class="px-4 py-3 text-center text-blue-700">{{ stats.totalCount }}</td>
+                  <td class="px-4 py-3 text-center text-green-700">{{ stats.activeCount }}</td>
+                  <td class="px-4 py-3 text-center text-gray-700">{{ stats.inactiveCount }}</td>
+                  <td class="px-4 py-3 text-center text-yellow-700">{{ stats.refundCount }}</td>
+                  <td class="px-4 py-3 text-center text-red-700">{{ stats.revokedCount }}</td>
+                </tr>
+              </tfoot>
+            </table>
+          </div>
+        </div>
+      </div>
+    </div>
   </PageContainer>
 </template>
 
 <style scoped>
-/* 已退款,未回收的行样式 - 橙底色 */
+/* 保持原来的行样式 */
 :deep(.row-refunded-only) {
   background-color: #fff7e6 !important;
 }
@@ -488,7 +704,6 @@ onMounted(() => {
   background-color: #ffe7ba !important;
 }
 
-/* 已退款,已回收的行样式 - 灰底色 */
 :deep(.row-refunded-revoked) {
   background-color: #f5f5f5 !important;
 }
@@ -496,4 +711,47 @@ onMounted(() => {
 :deep(.row-refunded-revoked:hover) {
   background-color: #e8e8e8 !important;
 }
+
+/* 统计信息展开动画 */
+.stats-container {
+  transition: all 0.3s ease;
+}
+
+/* 展开内容动画 */
+.stats-container > div:last-child {
+  animation: slideDown 0.3s ease-out;
+}
+
+@keyframes slideDown {
+  from {
+    opacity: 0;
+    transform: translateY(-10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .stats-container {
+    padding: 12px;
+  }
+
+  .flex-wrap > span {
+    flex: 1 0 calc(50% - 0.5rem);
+    margin-bottom: 0.5rem;
+  }
+
+  .stats-container > div:first-child {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 12px;
+  }
+
+  .stats-container > div:first-child > div:first-child {
+    width: 100%;
+  }
+}
 </style>

+ 43 - 7
src/views/activation-code/modules/header-search.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { Button, Form, Input, Select } from 'ant-design-vue';
+import { Button, Form, Input, Select, DatePicker } from 'ant-design-vue';
 
 import { $t } from '@/locales';
 
@@ -29,6 +29,7 @@ const handleReset = () => {
 const handleSearch = () => {
   emit('search');
 };
+
 </script>
 
 <template>
@@ -70,18 +71,46 @@ const handleSearch = () => {
               @update:value="updateSearchParams({ revoked: $event === undefined ? undefined : ($event === 'true') })"
             />
           </Form.Item>
-          <Form.Item label="是否已回收" name="revoked">
+
+          <!-- =========== 新增:状态筛选 =========== -->
+          <!-- <Form.Item label="激活状态" name="activated">
             <Select
-              :value="model.revoked === undefined ? undefined : String(model.revoked)"
-              placeholder="请选择回收状态"
+              :value="model.activated === undefined ? undefined : String(model.activated)"
+              placeholder="请选择状态"
               allow-clear
               :options="[
-                { label: '是', value: 'true' },
-                { label: '否', value: 'false' }
+                { label: '未使用', value: '0' },
+                { label: '已激活', value: '1' }
               ]"
-              @update:value="updateSearchParams({ revoked: $event === undefined ? undefined : ($event === 'true') })"
+              @update:value="updateSearchParams({ activated: $event === undefined ? undefined : Number($event) })"
+            />
+          </Form.Item> -->
+
+          <!-- =========== 新增:时间范围筛选 =========== -->
+          <Form.Item label="时间筛选" name="createTimeRange">
+            <DatePicker.RangePicker
+              style="width: 100%"
+              :value="model.startDate && model.endDate ? [dayjs(model.startDate), dayjs(model.endDate)] : undefined"
+              :show-time="{ format: 'HH:mm:ss' }"
+              format="YYYY-MM-DD HH:mm:ss"
+              placeholder="['开始时间', '结束时间']"
+              @change="(dates) => {
+                if (dates && dates[0] && dates[1]) {
+                  updateSearchParams({
+                    startDate: dates[0].format('YYYY-MM-DD HH:mm:ss'),
+                    endDate: dates[1].format('YYYY-MM-DD HH:mm:ss')
+                  });
+                } else {
+                  updateSearchParams({
+                    startDate: undefined,
+                    endDate: undefined
+                  });
+                }
+              }"
             />
           </Form.Item>
+
+
         </div>
         <div class="flex gap-12px">
           <Button type="primary" ghost @click="handleSearch">
@@ -95,3 +124,10 @@ const handleSearch = () => {
     </Form>
   </div>
 </template>
+
+<style scoped>
+/* 确保DatePicker.RangePicker在不同屏幕下正常显示 */
+:deep(.ant-picker) {
+  width: 100%;
+}
+</style>

+ 134 - 1
src/views/user-center/modules/change-password.vue

@@ -15,15 +15,29 @@ const authStore = useAuthStore();
 
 // 是否请求中
 const loading = ref(false);
+const adminLoading = ref(false); // 添加单独的加载状态
 
 const { formRef } = useAntdForm();
+const adminFormRef = useAntdForm(); // 添加第二个表单的ref
 
+// 主表单model(修改当前用户密码)
 const model: Api.SystemManage.EditPassword = reactive({
   oldPassword: '',
   password: '',
   confirmPassword: '',
 });
 
+// 管理员修改密码表单model - 分开两个独立的表单
+const admin2Model = reactive({
+  newPassword: '',
+  confirmPassword: '',
+});
+
+const admin3Model = reactive({
+  newPassword: '',
+  confirmPassword: '',
+});
+
 // 表单校验的 key
 type RuleKey = Extract<keyof Api.SystemManage.EditPassword, 'oldPassword' | 'password' | 'confirmPassword'>;
 
@@ -37,12 +51,22 @@ const rules = computed<Record<RuleKey, App.Global.FormRule | App.Global.FormRule
   };
 });
 
+// 管理员密码表单规则
+const createAdminRules = (password: string) => {
+  const { createConfirmPwdRule, formRules } = useFormRules();
+
+  return {
+    newPassword: formRules.pwd, // 使用密码规则
+    confirmPassword: createConfirmPwdRule(password || ''),
+  };
+};
+
 // 更新 model 的值
 const updateModel = (args: Partial<Api.SystemManage.SaveUserManage>) => {
   Object.assign(model, args);
 };
 
-// 提交数据
+// 提交当前用户密码修改
 async function handleSubmit(values: Api.SystemManage.EditPassword) {
   loading.value = true;
   // 请求接口
@@ -58,10 +82,59 @@ async function handleSubmit(values: Api.SystemManage.EditPassword) {
   }
   loading.value = false;
 }
+
+// 提交admin2密码修改
+async function handleAdmin2Submit() {
+  try {
+    adminLoading.value = true;
+    // 调用修改admin2密码的接口
+    // 这里假设可以直接修改,需要根据实际接口调整
+    const { error } = await changePassword({
+      targetUserName: 'admin2', // 固定用户名为admin2
+      newPassword: admin2Model.newPassword,
+    });
+
+    if (!error) {
+      window.$message?.success('修改 admin2 密码成功');
+      // 重置表单
+      admin2Model.newPassword = '';
+      admin2Model.confirmPassword = '';
+    }
+  } catch (error) {
+    console.error('修改admin2密码失败:', error);
+  } finally {
+    adminLoading.value = false;
+  }
+}
+
+// 提交admin3密码修改
+async function handleAdmin3Submit() {
+  try {
+    adminLoading.value = true;
+    // 调用修改admin3密码的接口
+    const { error } = await changePassword({
+      targetUserName: 'admin3', // 固定用户名为admin3
+      newPassword: admin3Model.newPassword,
+    });
+
+    if (!error) {
+      window.$message?.success('修改 admin3 密码成功');
+      // 重置表单
+      admin3Model.newPassword = '';
+      admin3Model.confirmPassword = '';
+    }
+  } catch (error) {
+    console.error('修改admin3密码失败:', error);
+  } finally {
+    adminLoading.value = false;
+  }
+}
 </script>
 
 <template>
+  <!-- 第一部分:修改当前用户密码 -->
   <AForm ref="formRef" layout="vertical" :model="model" :rules="rules" @finish="handleSubmit">
+    <h3 class="text-lg font-medium mb-4">{{ $t('form.changeCurrentPwd') || '修改当前用户密码' }}</h3>
     <ACol :span="24">
       <AFormItem name="oldPassword" :label="$t('form.oldPwd.label')">
         <AInputPassword v-model:value="model.oldPassword" size="large" :placeholder="$t('form.oldPwd.required')" />
@@ -74,4 +147,64 @@ async function handleSubmit(values: Api.SystemManage.EditPassword) {
       </AButton>
     </ARow>
   </AForm>
+
+  <ADivider />
+
+  <!-- 第二部分:修改admin2密码 -->
+  <AForm layout="vertical" :model="admin2Model" :rules="createAdminRules(admin2Model.newPassword)" @finish="handleAdmin2Submit">
+    <h3 class="text-lg font-medium mb-4">修改 admin2 密码</h3>
+    <ACol :span="24">
+      <AFormItem name="newPassword" label="新密码">
+        <AInputPassword
+          v-model:value="admin2Model.newPassword"
+          size="large"
+          placeholder="请输入admin2新密码"
+        />
+      </AFormItem>
+    </ACol>
+    <ACol :span="24">
+      <AFormItem name="confirmPassword" label="确认密码">
+        <AInputPassword
+          v-model:value="admin2Model.confirmPassword"
+          size="large"
+          placeholder="请确认admin2密码"
+        />
+      </AFormItem>
+    </ACol>
+    <ARow justify="center" class="mt-2">
+      <AButton type="primary" html-type="submit" :loading="adminLoading" block>
+        修改 admin2 密码
+      </AButton>
+    </ARow>
+  </AForm>
+
+  <ADivider />
+
+  <!-- 第三部分:修改admin3密码 -->
+  <AForm layout="vertical" :model="admin3Model" :rules="createAdminRules(admin3Model.newPassword)" @finish="handleAdmin3Submit">
+    <h3 class="text-lg font-medium mb-4">修改 admin3 密码</h3>
+    <ACol :span="24">
+      <AFormItem name="newPassword" label="新密码">
+        <AInputPassword
+          v-model:value="admin3Model.newPassword"
+          size="large"
+          placeholder="请输入admin3新密码"
+        />
+      </AFormItem>
+    </ACol>
+    <ACol :span="24">
+      <AFormItem name="confirmPassword" label="确认密码">
+        <AInputPassword
+          v-model:value="admin3Model.confirmPassword"
+          size="large"
+          placeholder="请确认admin3密码"
+        />
+      </AFormItem>
+    </ACol>
+    <ARow justify="center" class="mt-2">
+      <AButton type="primary" html-type="submit" :loading="adminLoading" block>
+        修改 admin3 密码
+      </AButton>
+    </ARow>
+  </AForm>
 </template>