Просмотр исходного кода

feat: 新增交付伙伴,重置密码

lixuan недель назад: 4
Родитель
Сommit
da6722d12b

+ 1 - 1
apps/web-velofex/.env.production

@@ -1,7 +1,7 @@
 VITE_BASE=./
 
 # 接口地址
-VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
+VITE_GLOB_API_URL=http://a.dev.jbpm.shalu.com
 
 # 是否开启压缩,可以设置为 none, brotli, gzip
 VITE_COMPRESS=none

+ 140 - 4
apps/web-velofex/src/api/core/user.ts

@@ -100,6 +100,22 @@ export namespace UserApi {
     usedWorkFlow: number;
   }
 
+  export interface UserModel {
+    accessId: string;
+    account: string;
+    cellPhone: string;
+    chineseName: string;
+    englishName: string;
+    id: string;
+    isActive: boolean;
+    isSuperAdmin: boolean;
+    langName: string;
+    language: string;
+    name: string;
+    expiredTime?: string;
+    emailAddress?: string;
+  }
+
   export interface LangName {
     name: string;
     value: string;
@@ -217,7 +233,7 @@ export namespace UserApi {
     currentPage: number;
     hasNextPage: boolean;
     hasPreviousPage: boolean;
-    model: DeliveryPartnerModel[];
+    model: UserModel[];
     pageSize: number;
     totalCount: number;
     totalPages: number;
@@ -353,6 +369,57 @@ export namespace UserApi {
     };
     isAuthorized: boolean;
   }
+
+  export interface AddUserParams {
+    langNameList: Array<{
+      name: string;
+      value: string;
+    }>;
+    expiredTime: string;
+    langName: null | string;
+    account: string;
+    password: string;
+    cellPhone: string;
+    emailAddress: string;
+    gogs_email: string;
+    isActive?: boolean;
+  }
+
+  export interface UpdateUserParams {
+    id: string;
+    langNameList: Array<{
+      name: string;
+      value: string;
+    }>;
+    expiredTime: string;
+    langName: null | string;
+    account: string;
+    password: string;
+    cellPhone: string;
+    emailAddress: string;
+    gogs_email: string;
+    isActive?: boolean;
+  }
+
+  export interface UserResult {
+    isSuccess: boolean;
+    code: number;
+    result: boolean;
+    isAuthorized: boolean;
+  }
+
+  export interface ProjectModel {
+    id: string;
+    name: string;
+    code: string;
+  }
+
+  export interface GetProjectsResult {
+    isSuccess: boolean;
+    code: number;
+    result: ProjectModel[];
+    isAuthorized: boolean;
+  }
 }
 
 /**
@@ -387,10 +454,13 @@ export async function getDeliveryPartnersApi(
 }
 
 /**
- * 获取合作伙伴
+ * 重置密码
  */
-export async function getPartnersApi() {
-  return requestClient.post('api/partner/AllList');
+export async function resetPasswordApi(data: {
+  newPassword: string;
+  userId: string;
+}) {
+  return requestClient.post('/api/account/doResetPassword', data);
 }
 
 /**
@@ -576,3 +646,69 @@ export async function deleteApplicationApi(data: { id: string }) {
     data,
   );
 }
+
+/**
+ * 添加用户
+ */
+export async function addUserApi(data: UserApi.AddUserParams) {
+  return requestClient.post<UserApi.UserResult>('/api/user/doCreate', data);
+}
+
+/**
+ * 更新用户
+ */
+export async function updateUserApi(data: UserApi.UpdateUserParams) {
+  return requestClient.post<UserApi.UserResult>('/api/user/doUpdate', data);
+}
+
+/**
+ * 删除用户
+ */
+export async function deleteUserApi(data: any) {
+  return requestClient.post<UserApi.UserResult>('/api/user/doDelete', data);
+}
+
+/**
+ * 获取用户关联项目
+ */
+export async function getAssociatedProjectsApi(userId: string) {
+  return requestClient.post<UserApi.GetProjectsResult>(
+    '/api/user/getAssociatedProjects',
+    { userId },
+  );
+}
+
+/**
+ * 添加项目到用户
+ */
+export async function addProjectToUserApi(projectId: string, userId: string) {
+  return requestClient.post<UserApi.UserResult>('/api/user/addProject', {
+    projectId,
+    userId,
+  });
+}
+
+/**
+ * 从用户删除项目
+ */
+export async function deleteProjectFromUserApi(
+  projectId: string,
+  userIds: string[],
+) {
+  return requestClient.post<UserApi.UserResult>('/api/user/deleteProject', {
+    projectId,
+    userIds,
+  });
+}
+
+/**
+ * 获取可用项目
+ */
+export async function getAvailableProjectsApi(
+  data: UserApi.GetMyApplicationListParams,
+) {
+  return requestClient.post<UserApi.GetMyApplicationListResult>(
+    '/api/enterprise/myList',
+    data,
+  );
+}

+ 10 - 0
apps/web-velofex/src/router/routes/external/router-a.ts

@@ -52,6 +52,16 @@ const routes: RouteRecordRaw[] = [
           title: $t('homeModule.salesPartners'),
         },
       },
+      {
+        name: 'DeliveryPartners',
+        path: '/delivery-partners',
+        component: () =>
+          import('#/views/dashboard/delivery-partners/index.vue'),
+        meta: {
+          icon: 'carbon:truck',
+          title: $t('homeModule.deliveryPartners'),
+        },
+      },
     ],
   },
 ];

+ 649 - 0
apps/web-velofex/src/views/dashboard/delivery-partners/index.vue

@@ -0,0 +1,649 @@
+<script setup lang="ts">
+import { computed, onMounted, ref, watch } from 'vue';
+
+import { useUserStore } from '@vben/stores';
+
+import { $t } from '@/locales';
+import {
+  Button,
+  Input,
+  message,
+  Modal,
+  Pagination,
+  Select,
+  Table,
+} from 'antdv-next';
+import MD5 from 'crypto-js/md5';
+
+import {
+  deleteUserApi,
+  getDeliveryPartnersApi,
+  getPartnersListApi,
+  resetPasswordApi,
+  type UserApi,
+} from '#/api';
+
+import DeliveryPartnersModal from './delivery-partners-modal.vue';
+
+const modalOpen = ref(false);
+const modalMode = ref<'add' | 'edit'>('add');
+const currentUser = ref<any>(null);
+
+const resetPasswordModalOpen = ref(false);
+const resetPasswordUser = ref<any>(null);
+const newPassword = ref('');
+
+const userStore = useUserStore();
+const isLogin = computed(() => !!userStore.userInfo);
+
+const userList = ref<UserApi.UserModel[]>([]);
+const loading = ref(false);
+
+const searchParams = ref({
+  account: '',
+  chineseName: '',
+  englishName: '',
+  cellPhone: '',
+  emailAddress: '',
+  isActive: '1',
+  currentPage: 1,
+  pageSize: 10,
+  partnerInfoId: '',
+});
+
+const totalPage = ref(0);
+const totalCount = ref(0);
+
+const partners = ref<Array<{ label: string; value: string }>>([]);
+
+function handleSearch() {
+  searchParams.value.currentPage = 1;
+  fetchUserList();
+}
+
+function handleAddNew() {
+  modalMode.value = 'add';
+  currentUser.value = null;
+  modalOpen.value = true;
+}
+
+function handleEdit(item: any) {
+  modalMode.value = 'edit';
+  currentUser.value = item;
+  modalOpen.value = true;
+}
+
+function handleResetPassword(item: any) {
+  resetPasswordUser.value = item;
+  newPassword.value = '';
+  resetPasswordModalOpen.value = true;
+}
+
+function handleModalSave() {
+  modalOpen.value = false;
+  fetchUserList();
+}
+
+function handleBack() {
+  window.history.back();
+}
+
+function handleClear() {
+  searchParams.value.account = '';
+  searchParams.value.chineseName = '';
+  searchParams.value.englishName = '';
+  searchParams.value.cellPhone = '';
+  searchParams.value.emailAddress = '';
+  searchParams.value.isActive = '1';
+  searchParams.value.partnerInfoId = '';
+  handleSearch();
+}
+
+function handlePageChange(page: number) {
+  searchParams.value.currentPage = page;
+  fetchUserList();
+}
+
+async function fetchUserList() {
+  if (!isLogin.value) {
+    return;
+  }
+
+  try {
+    loading.value = true;
+    const result = await getDeliveryPartnersApi({
+      currentPage: searchParams.value.currentPage,
+      pageSize: searchParams.value.pageSize,
+      orderByProperty: 'Id',
+      Ascending: false,
+      totalPage: 1,
+      totalCount: 7,
+      filters: [
+        { name: 'account', value: searchParams.value.account },
+        { name: 'chineseName', value: searchParams.value.chineseName },
+        { name: 'englishName', value: searchParams.value.englishName },
+        { name: 'cellPhone', value: searchParams.value.cellPhone },
+        { name: 'emailAddress', value: searchParams.value.emailAddress },
+        { name: 'isActive', value: searchParams.value.isActive },
+        { name: 'partnerInfoId', value: searchParams.value.partnerInfoId },
+      ],
+    });
+    if (result?.result?.model) {
+      userList.value = result.result.model;
+      totalPage.value = result.result.totalPages;
+      totalCount.value = result.result.totalCount;
+    }
+  } catch (error) {
+    console.error('获取用户列表失败:', error);
+  } finally {
+    loading.value = false;
+  }
+}
+
+async function deleteUser(item: any) {
+  if (!isLogin.value) {
+    return;
+  }
+
+  try {
+    loading.value = true;
+    const result = await deleteUserApi(item);
+    if (result?.result) {
+      fetchUserList();
+      message.success('删除成功');
+    }
+  } catch (error) {
+    console.error('删除用户失败:', error);
+  } finally {
+    loading.value = false;
+  }
+}
+
+function handleDelete(item: any) {
+  Modal.confirm({
+    title: '删除确认',
+    content: '确定要删除此用户吗?',
+    okText: '是',
+    okType: 'danger',
+    cancelText: '否',
+    onOk() {
+      deleteUser(item);
+    },
+    onCancel() {},
+  });
+}
+
+async function handleResetPasswordConfirm() {
+  if (!newPassword.value) {
+    message.error('请输入要重置的密码');
+    return;
+  }
+
+  try {
+    loading.value = true;
+    const encryptedPassword = MD5(newPassword.value).toString();
+    const result = await resetPasswordApi({
+      userId: resetPasswordUser.value.id,
+      newPassword: encryptedPassword,
+    });
+    if (result?.result) {
+      message.success('密码重置成功');
+      resetPasswordModalOpen.value = false;
+    }
+  } catch (error) {
+    console.error('重置密码失败:', error);
+    message.error('重置密码失败');
+  } finally {
+    loading.value = false;
+  }
+}
+
+watch(
+  () => isLogin.value,
+  (newValue) => {
+    if (newValue) {
+      fetchUserList();
+    }
+  },
+  { immediate: true },
+);
+
+onMounted(async () => {
+  if (!isLogin.value) {
+    return;
+  }
+
+  try {
+    const result = await getPartnersListApi({
+      currentPage: 1,
+      pageSize: 10,
+    });
+    if (result?.result?.model) {
+      partners.value = result.result.model.map(
+        (item: { id: string; name: string }) => ({
+          value: item.id,
+          label: item.name,
+        }),
+      );
+    }
+  } catch (error) {
+    console.error('获取合作伙伴列表失败:', error);
+  }
+});
+</script>
+
+<template>
+  <div class="p-5">
+    <div class="mb-4 flex items-center justify-between">
+      <div class="text-sm text-[#462424] text-gray-500">
+        {{ $t('homeModule.deliveryPartners') }}
+      </div>
+      <div
+        class="flex cursor-pointer items-center gap-[9px] text-[16px] font-bold text-[#462424]"
+        @click="handleBack"
+      >
+        <div
+          class="global-color flex h-[22px] w-[22px] items-center justify-center rounded-full"
+        >
+          <img
+            alt=""
+            class="h-[11px] w-[10px]"
+            src="@/assets/image/the-left.png"
+          />
+        </div>
+        {{ $t('btn.back') }}
+      </div>
+    </div>
+
+    <div class="mb-[21px] mt-[30px] text-[26px] font-bold text-[#462424]">
+      {{ $t('homeModule.deliveryPartners') }}
+    </div>
+
+    <div class="mb-4 space-y-4">
+      <div class="flex items-center gap-4">
+        <div class="flex flex-col gap-1">
+          <label class="text-[11px] text-[#000]">账号</label>
+          <Input
+            v-model:value="searchParams.account"
+            class="h-[42px] w-[138px] rounded-[11px] border-[#707070]"
+            placeholder=""
+          />
+        </div>
+        <div class="flex flex-col gap-1">
+          <label class="text-[11px] text-[#000]">中文姓名</label>
+          <Input
+            v-model:value="searchParams.chineseName"
+            class="h-[42px] w-[97.81px] rounded-[11px] border-[#707070]"
+            placeholder=""
+          />
+        </div>
+        <div class="flex flex-col gap-1">
+          <label class="text-[11px] text-[#000]">英文姓名</label>
+          <Input
+            v-model:value="searchParams.englishName"
+            class="h-[42px] w-[138px] rounded-[11px] border-[#707070]"
+            placeholder=""
+          />
+        </div>
+      </div>
+      <div class="flex items-center gap-4">
+        <div class="flex flex-col gap-1">
+          <label class="text-[11px] text-[#000]">绑定手机</label>
+          <Input
+            v-model:value="searchParams.cellPhone"
+            class="h-[42px] w-[138px] rounded-[11px] border-[#707070]"
+            placeholder=""
+          />
+        </div>
+        <div class="flex flex-col gap-1">
+          <label class="text-[11px] text-[#000]">邮箱</label>
+          <Input
+            v-model:value="searchParams.emailAddress"
+            class="h-[42px] w-[168px] rounded-[11px] border-[#707070]"
+            placeholder=""
+          />
+        </div>
+        <div class="flex flex-col gap-1">
+          <label class="text-[11px] text-[#000]">Cooperate Partner</label>
+          <Select
+            v-model:value="searchParams.partnerInfoId"
+            :options="partners"
+            class="h-[42px] w-[168px] rounded-[11px] border-[#707070]"
+            placeholder=""
+          />
+        </div>
+        <div class="ml-[58px] flex flex-col gap-1">
+          <label class="text-[11px] text-[#000]">是否启用</label>
+          <div class="flex h-[42px] items-center gap-2">
+            <label
+              class="flex cursor-pointer items-center gap-2 text-sm"
+              @click="searchParams.isActive = '1'"
+            >
+              <div
+                class="flex h-[20px] w-[20px] items-center justify-center rounded-[5px] border-[1px] border-[#707070]"
+              >
+                <div
+                  v-if="searchParams.isActive === '1'"
+                  class="h-[14px] w-[14px] flex-shrink-0 rounded-[3px] bg-[#462424]"
+                ></div>
+              </div>
+              {{ $t('btn.yes') }}
+            </label>
+            <label
+              class="flex cursor-pointer items-center gap-2 text-sm"
+              @click="searchParams.isActive = '0'"
+            >
+              <div
+                class="flex h-[20px] w-[20px] items-center justify-center rounded-[5px] border-[1px] border-[#707070]"
+              >
+                <div
+                  v-if="searchParams.isActive === '0'"
+                  class="h-[14px] w-[14px] flex-shrink-0 rounded-[3px] bg-[#462424]"
+                ></div>
+              </div>
+              {{ $t('btn.no') }}
+            </label>
+          </div>
+        </div>
+        <div class="ml-auto flex items-center gap-2">
+          <Button class="h-[42px]" @click="handleSearch">
+            <svg
+              class="h-[21.5px] w-[21.5px] cursor-pointer"
+              fill="none"
+              viewBox="0 0 24 24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <circle
+                cx="11"
+                cy="11"
+                r="8"
+                stroke="currentColor"
+                stroke-width="2"
+              />
+              <path
+                d="M21 21L16.65 16.65"
+                stroke="currentColor"
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+              />
+            </svg>
+            {{ $t('btn.search') }}
+          </Button>
+          <Button class="h-[42px]" @click="handleClear">
+            <svg
+              class="h-[21.5px] w-[21.5px] cursor-pointer"
+              fill="none"
+              viewBox="0 0 24 24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M3 6H5"
+                stroke="currentColor"
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+              />
+              <path
+                d="M19 6V20C19 21.1046 18.1046 22 17 22H7C5.89543 22 5 21.1046 5 20V6M8 6V4C8 3.79086 8.79086 3 10 3H14C15.2091 3 16 3.79086 16 6V8M10 11V17M14 11V17"
+                stroke="currentColor"
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+              />
+            </svg>
+            {{ $t('btn.reset') }}
+          </Button>
+          <Button class="h-[42px]" type="primary" @click="handleAddNew">
+            <img
+              alt=""
+              class="h-[21.5px] w-[21.5px] cursor-pointer"
+              src="@/assets/image/new.png"
+            />
+            {{ $t('btn.addNew') }}
+          </Button>
+        </div>
+      </div>
+    </div>
+
+    <div v-if="loading" class="py-8 text-center">加载中...</div>
+    <div v-else>
+      <Table
+        :columns="[
+          {
+            title: '账号',
+            dataIndex: 'account',
+            key: 'account',
+            width: 130,
+          },
+          {
+            title: '名称',
+            dataIndex: 'name',
+            width: 130,
+            key: 'name',
+          },
+          {
+            title: '有效期',
+            dataIndex: 'expiredTime',
+            key: 'expiredTime',
+            width: 130,
+          },
+          {
+            title: '绑定手机',
+            dataIndex: 'cellPhone',
+            width: 130,
+            key: 'cellPhone',
+          },
+          {
+            title: '邮箱',
+            dataIndex: 'emailAddress',
+            width: 130,
+            key: 'emailAddress',
+          },
+          {
+            title: '用户秘钥',
+            dataIndex: 'accessId',
+            key: 'accessId',
+          },
+          {
+            title: '状态',
+            width: 130,
+            key: 'status',
+          },
+          {
+            title: '操作',
+            key: 'action',
+          },
+        ]"
+        :data-source="userList"
+        :loading="loading"
+        :show-size-changer="false"
+        class="mt-[38px]"
+      >
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'status'">
+            <span v-if="record.isActive" class="text-green-600">启用</span>
+            <span v-else class="text-red-600">禁用</span>
+          </template>
+          <template v-if="column.key === 'action'">
+            <div class="flex gap-2">
+              <div class="cursor-pointer" @click="handleEdit(record)">
+                <svg
+                  class="mr-1 h-[16px] w-[16px]"
+                  fill="none"
+                  viewBox="0 0 24 24"
+                  xmlns="http://www.w3.org/2000/svg"
+                >
+                  <path
+                    d="M11 4H4C3.46957 4 2.96086 4.21071 2.58579 4.58579C2.21071 4.96086 2 5.46957 2 6V19C2 19.5304 2.21071 20.0391 2.58579 20.4142C2.96086 20.7893 3.46957 21 4 21H17C17.5304 21 18.0391 20.7893 18.4142 20.4142C18.7893 20.0391 19 19.5304 19 19V12"
+                    stroke="currentColor"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                    stroke-width="2"
+                  />
+                  <path
+                    d="M18.5 2.5C18.8978 2.10218 19.4374 1.87868 20 1.87868C20.5626 1.87868 21.1022 2.10218 21.5 2.5C21.8978 2.89782 22.1213 3.43743 22.1213 4C22.1213 4.56257 21.8978 5.10218 21.5 5.5L12 15L9 12L18.5 2.5Z"
+                    stroke="currentColor"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                    stroke-width="2"
+                  />
+                </svg>
+              </div>
+              <div class="cursor-pointer" @click="handleResetPassword(record)">
+                <svg
+                  class="mr-1 h-[16px] w-[16px]"
+                  fill="none"
+                  viewBox="0 0 24 24"
+                  xmlns="http://www.w3.org/2000/svg"
+                >
+                  <circle
+                    cx="12"
+                    cy="12"
+                    r="10"
+                    stroke="currentColor"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                    stroke-width="2"
+                  />
+                  <line
+                    stroke="currentColor"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                    stroke-width="2"
+                    x1="12"
+                    x2="12"
+                    y1="6"
+                    y2="18"
+                  />
+                </svg>
+              </div>
+              <div class="cursor-pointer" @click="handleDelete(record)">
+                <svg
+                  class="mr-1 h-[16px] w-[16px]"
+                  fill="none"
+                  viewBox="0 0 24 24"
+                  xmlns="http://www.w3.org/2000/svg"
+                >
+                  <path
+                    d="M3 6H5H21"
+                    stroke="currentColor"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                    stroke-width="2"
+                  />
+                  <path
+                    d="M19 6V20C19 20.5304 18.7893 21.0391 18.4142 21.4142C18.0391 21.7893 17.5304 22 17 22H7C6.46957 22 5.96086 21.7893 5.58579 21.4142C5.21071 21.0391 5 20.5304 5 20V6M8 6V4C8 3.46957 8.46957 3 9 3H15C15.5304 3 16 3.46957 16 4V6"
+                    stroke="currentColor"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                    stroke-width="2"
+                  />
+                </svg>
+              </div>
+            </div>
+          </template>
+        </template>
+      </Table>
+
+      <div class="list-pagination mt-4">
+        <Pagination
+          v-model:current="searchParams.currentPage"
+          :hide-on-single-page="true"
+          :page-size="searchParams.pageSize"
+          :show-size-changer="false"
+          :total="totalCount"
+          position="bottomLeft"
+          @change="handlePageChange"
+        />
+      </div>
+    </div>
+
+    <DeliveryPartnersModal
+      v-model:open="modalOpen"
+      :mode="modalMode"
+      :user-data="currentUser"
+      @save="handleModalSave"
+    />
+
+    <Modal
+      v-model:open="resetPasswordModalOpen"
+      title="重置密码"
+      width="500"
+      @ok="handleResetPasswordConfirm"
+    >
+      <div class="space-y-4">
+        <div class="flex flex-col gap-2">
+          <label class="text-sm font-medium">新密码</label>
+          <Input
+            v-model:value="newPassword"
+            placeholder="请输入新密码"
+            type="password"
+          />
+        </div>
+      </div>
+    </Modal>
+  </div>
+</template>
+
+<style scoped>
+:deep(.ant-table) {
+  background: transparent !important;
+}
+
+:deep(.ant-table-thead > tr > th) {
+  font-size: 11px !important;
+  background: transparent !important;
+}
+
+:deep(.ant-table-tbody > tr:nth-child(odd) > td) {
+  font-size: 11px !important;
+  background: transparent !important;
+}
+
+:deep(.ant-table-tbody > tr:nth-child(even) > td) {
+  font-size: 11px !important;
+  background: #f5f3f3 !important;
+}
+
+:deep(.ant-table-tbody > tr:hover > td) {
+  background: transparent !important;
+}
+
+:deep(.ant-table-container) {
+  background: transparent !important;
+}
+
+:deep(.ant-table-wrapper) {
+  background: transparent !important;
+}
+
+:deep(
+  .ant-table-wrapper
+    .ant-table-thead
+    > tr
+    > th:not(:last-child):not(.ant-table-selection-column):not(
+      .ant-table-row-expand-icon-cell
+    ):not([colspan])::before,
+  .ant-table-wrapper
+    .ant-table-thead
+    > tr
+    > td:not(:last-child):not(.ant-table-selection-column):not(
+      .ant-table-row-expand-icon-cell
+    ):not([colspan])::before
+) {
+  display: none !important;
+}
+</style>
+
+<style lang="scss">
+.ant-pagination-item-active {
+  color: #7a003d !important;
+  background-color: #f8f2f5 !important;
+  border-color: transparent !important;
+
+  a {
+    color: #c48da8 !important;
+  }
+}
+</style>

+ 11 - 1
apps/web-velofex/src/views/dashboard/home/delivery-partners.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import { computed, ref, watch } from 'vue';
+import { useRouter } from 'vue-router';
 
 import { useUserStore } from '@vben/stores';
 
@@ -7,6 +8,12 @@ import { $t } from '@/locales';
 
 import { getDeliveryPartnersApi, type UserApi } from '#/api';
 
+const router = useRouter();
+
+function handleNavigate() {
+  router.push('/delivery-partners');
+}
+
 const userStore = useUserStore();
 const isLogin = computed(() => !!userStore.userInfo);
 
@@ -55,7 +62,10 @@ watch(
 </script>
 
 <template>
-  <div class="info-card border-box h-[217px] w-full rounded-lg p-5 shadow-md">
+  <div
+    class="info-card border-box h-[217px] w-full cursor-pointer rounded-lg p-5 shadow-md"
+    @click="handleNavigate"
+  >
     <div class="items-flex-start flex justify-between">
       <div>
         <h2 class="text-lg font-bold">