Parcourir la source

feat: 应用详情新增,编辑完成。列表查询,分页调整。新增全局按钮样式

lixuan il y a 1 semaine
Parent
commit
dfa22489bb

+ 171 - 1
apps/web-velofex/src/api/core/user.ts

@@ -147,6 +147,71 @@ export namespace UserApi {
     isAuthorized: boolean;
     isAuthorized: boolean;
   }
   }
 
 
+  export interface GenerateSystemSecretParams {
+    begin_date: any;
+    encrypt_type: number;
+    enterprise_code: string;
+    error_msg: string;
+    is_permanent: boolean;
+    period_day: number;
+  }
+
+  export interface GenerateSystemSecretResult {
+    isSuccess: boolean;
+    code: number;
+    result: any;
+    isAuthorized: boolean;
+  }
+
+  export interface ApplicationDetailResult {
+    isSuccess: boolean;
+    code: number;
+    result: any;
+    isAuthorized: boolean;
+  }
+
+  export interface AssociatedUser {
+    id: string;
+    account: string;
+    role: string;
+  }
+
+  export interface GetAssociatedUsersResult {
+    isSuccess: boolean;
+    code: number;
+    result: AssociatedUser[];
+    isAuthorized: boolean;
+  }
+
+  export interface AssociateUserParams {
+    enterpriseId: string;
+    userId: string;
+    roleTypes: string;
+  }
+
+  export interface AssociateUserResult {
+    isSuccess: boolean;
+    code: number;
+    result: boolean;
+    isAuthorized: boolean;
+  }
+
+  export interface GetSystemSecretResult {
+    isSuccess: boolean;
+    code: number;
+    result: {
+      encryptInfo: string;
+      generateSystemEncryptInfo: {
+        begin_date: string;
+        error_msg: string;
+        is_permanent: boolean;
+        period_day: number;
+        system_key: string;
+      };
+    };
+    isAuthorized: boolean;
+  }
+
   export interface GetDeliveryPartnersResult {
   export interface GetDeliveryPartnersResult {
     result: any;
     result: any;
     currentPage: number;
     currentPage: number;
@@ -206,6 +271,13 @@ export async function getCloneApplicationsApi(
   return requestClient.post('/api/enterprise/myList', data);
   return requestClient.post('/api/enterprise/myList', data);
 }
 }
 
 
+/**
+ * 获取客户关系
+ */
+export async function getCustomerRelationsApi() {
+  return requestClient.post('/api/enterclient/alllist');
+}
+
 /**
 /**
  * 添加应用
  * 添加应用
  */
  */
@@ -217,7 +289,105 @@ export async function addApplicationApi(data: UserApi.AddApplicationParams) {
 }
 }
 
 
 /**
 /**
- * 添加 删除应用
+ * 更新应用
+ */
+export async function updateApplicationApi(data: UserApi.AddApplicationParams) {
+  return requestClient.post<UserApi.AddApplicationResult>(
+    '/api/enterprise/doUpdate',
+    data,
+  );
+}
+
+/**
+ * 获取应用详情
+ */
+export async function getApplicationDetailApi(code: string) {
+  return requestClient.post<UserApi.ApplicationDetailResult>(
+    '/api/enterprise/info',
+    { code },
+  );
+}
+
+/**
+ * 获取关联用户
+ */
+export async function getAssociatedUsersApi(enterprise_code: string) {
+  return requestClient.post<UserApi.GetAssociatedUsersResult>(
+    '/api/enterprise/listEnterpriseUser',
+    { enterprise_code },
+  );
+}
+
+/**
+ * 获取可添加用户
+ */
+export async function getAddUsersApi(data: UserApi.GetMyApplicationListParams) {
+  return requestClient.post<UserApi.GetMyApplicationListResult>(
+    '/api/bpm/GetAllEnterpriseUser',
+    data,
+  );
+}
+
+/**
+ * 关联用户保存
+ */
+export async function associateUserApi(data: UserApi.AssociateUserParams) {
+  return requestClient.post<UserApi.AssociateUserResult>(
+    '/api/enterprise/updateEnterpriseUser',
+    data,
+  );
+}
+
+/**
+ * 给应用添加用户
+ */
+export async function addUserToApplicationApi(
+  account: string,
+  enterprise_code: string,
+) {
+  return requestClient.post<UserApi.AssociateUserResult>(
+    '/api/enterprise/tryAddEnterpriseUser',
+    { account, enterprise_code },
+  );
+}
+
+/**
+ * 删除应用用户
+ */
+export async function deleteUserFromApplicationApi(
+  userId: string,
+  enterprise_code: string[],
+) {
+  return requestClient.post<UserApi.AssociateUserResult>(
+    '/api/user/doDeleteUserEnterprise',
+    { userId, enterprise_code },
+  );
+}
+
+/**
+ * 获取系统密钥
+ */
+export async function getSystemSecretApi(enterprise_code: string) {
+  return requestClient.post<UserApi.GetSystemSecretResult>(
+    '/api/enterprise/getSystemEncryptInfo',
+    { enterprise_code },
+  );
+}
+
+/**
+ * 生成系统密钥
+ */
+export async function generateSystemSecretApi(
+  data: UserApi.GenerateSystemSecretParams,
+) {
+  return requestClient.post<UserApi.GenerateSystemSecretResult>(
+    '/api/enterprise/doGenerateSystemEncryptInfo',
+    data,
+  );
+}
+
+/**
+ * 删除应用
  */
  */
 export async function deleteApplicationApi(data: { id: string }) {
 export async function deleteApplicationApi(data: { id: string }) {
   return requestClient.post<UserApi.AddApplicationResult>(
   return requestClient.post<UserApi.AddApplicationResult>(

+ 2 - 0
apps/web-velofex/src/bootstrap.ts

@@ -14,6 +14,8 @@ import { initComponentAdapter } from './adapter/component';
 import App from './app.vue';
 import App from './app.vue';
 import { router } from './router';
 import { router } from './router';
 
 
+import './styles/global.scss';
+
 async function bootstrap(namespace: string) {
 async function bootstrap(namespace: string) {
   // 初始化组件适配器
   // 初始化组件适配器
   await initComponentAdapter();
   await initComponentAdapter();

+ 54 - 0
apps/web-velofex/src/styles/global.scss

@@ -0,0 +1,54 @@
+.ant-btn {
+  min-height: 33px;
+  padding: 0 20px;
+  font-weight: 500;
+  border-radius: 25px;
+}
+
+.ant-btn-primary {
+  position: relative;
+  z-index: 1;
+  overflow: hidden;
+  color: #fff;
+  background: linear-gradient(to right, #8b0046, #460023);
+  border: none !important;
+  outline: none !important;
+  box-shadow: none !important;
+}
+
+.ant-btn-primary::after {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: -1;
+  width: 100%;
+  height: 100%;
+  content: '';
+  background: #7a003d;
+  opacity: 0;
+  transition: opacity 0.3s ease;
+}
+
+.ant-btn-primary:hover {
+  color: #fff;
+  border: none !important;
+  outline: none !important;
+  box-shadow: none !important;
+}
+
+.ant-btn-primary:hover::after {
+  opacity: 1;
+}
+
+.ant-btn-default {
+  color: #000;
+  background: transparent;
+  border: 1px solid #000;
+}
+
+.ant-btn-default:hover {
+  color: #fff !important;
+  background: linear-gradient(to right, #8b0046, #460023);
+  border: 1px solid #8b0046 !important;
+  border-color: #8b0046;
+}

+ 360 - 72
apps/web-velofex/src/views/dashboard/application-management/application-modal.vue

@@ -1,4 +1,6 @@
 <script setup lang="ts">
 <script setup lang="ts">
+import type { Dayjs } from 'dayjs';
+
 import { computed, ref, watch } from 'vue';
 import { computed, ref, watch } from 'vue';
 
 
 import {
 import {
@@ -13,11 +15,22 @@ import {
   Table,
   Table,
   Upload,
   Upload,
 } from 'antdv-next';
 } from 'antdv-next';
+import dayjs from 'dayjs';
 
 
 import {
 import {
   addApplicationApi,
   addApplicationApi,
+  addUserToApplicationApi,
+  associateUserApi,
+  deleteUserFromApplicationApi,
+  generateSystemSecretApi,
+  getAddUsersApi,
+  getApplicationDetailApi,
+  getAssociatedUsersApi,
   getCloneApplicationsApi,
   getCloneApplicationsApi,
+  getCustomerRelationsApi,
   getPartnersApi,
   getPartnersApi,
+  getSystemSecretApi,
+  updateApplicationApi,
 } from '#/api';
 } from '#/api';
 
 
 interface Props {
 interface Props {
@@ -38,9 +51,12 @@ const activeMenu = ref('basic');
 const token = localStorage.getItem('token_a');
 const token = localStorage.getItem('token_a');
 
 
 const formData = ref({
 const formData = ref({
+  code: '',
+  id: '',
   logo: null,
   logo: null,
   fileId: '',
   fileId: '',
   imgPhotoFileId: '',
   imgPhotoFileId: '',
+  fileList: [] as any[],
   partner: '',
   partner: '',
   projectCode: '',
   projectCode: '',
   cloneProject: '',
   cloneProject: '',
@@ -62,8 +78,8 @@ const formData = ref({
 });
 });
 
 
 const keyData = ref({
 const keyData = ref({
-  startTime: null,
-  encryptionType: '',
+  startTime: null as Dayjs | null,
+  encryptionType: 'DES3',
   validDays: 0,
   validDays: 0,
   isPermanent: false,
   isPermanent: false,
   errorDescription: '',
   errorDescription: '',
@@ -83,12 +99,23 @@ const menuItems = [
 ];
 ];
 
 
 const roleOptions = [
 const roleOptions = [
-  { value: 'normal', label: '普通人员' },
-  { value: 'parttime', label: '兼职人员' },
+  { value: '普通人员', label: '普通人员' },
+  { value: '兼职人员', label: '兼职人员' },
+  { value: '内部人员', label: '内部人员' },
+  { value: '管理员', label: '管理员' },
 ];
 ];
 
 
 const applicationMenu = ref<Array<{ label: string; value: string }>>([]);
 const applicationMenu = ref<Array<{ label: string; value: string }>>([]);
 const applications = ref<Array<{ label: string; value: string }>>([]);
 const applications = ref<Array<{ label: string; value: string }>>([]);
+const customerRelationsOptions = ref<Array<{ label: string; value: string }>>(
+  [],
+);
+const allUsers = ref<any[]>([]);
+const userPagination = ref({
+  currentPage: 1,
+  pageSize: 10,
+  total: 0,
+});
 
 
 async function fetchPartners() {
 async function fetchPartners() {
   try {
   try {
@@ -101,9 +128,7 @@ async function fetchPartners() {
         }),
         }),
       );
       );
     }
     }
-  } catch (error) {
-    console.error('获取合作伙伴失败:', error);
-  }
+  } catch {}
 }
 }
 
 
 async function fetchCloneApplications() {
 async function fetchCloneApplications() {
@@ -125,22 +150,142 @@ async function fetchCloneApplications() {
         }),
         }),
       );
       );
     }
     }
-  } catch (error) {
-    console.error('获取可以克隆的项目失败:', error);
-  }
+  } catch {}
 }
 }
 
 
-const allUsers = ref<any[]>([]);
+async function fetchCustomerRelations() {
+  try {
+    const result = await getCustomerRelationsApi();
+    if (result?.result) {
+      customerRelationsOptions.value = result.result.map(
+        (item: { code: string; name: string }) => ({
+          value: item.code,
+          label: item.name,
+        }),
+      );
+    }
+  } catch {}
+}
+
+async function fetchApplicationDetail() {
+  try {
+    const result = await getApplicationDetailApi(formData.value.code);
+    if (result?.result) {
+      const data = result.result;
+      formData.value.code = data.code;
+      formData.value.projectCode = data.code;
+      formData.value.cloneProject = data.from_code;
+      formData.value.contact = data.contact;
+      formData.value.phone = data.cellPhone;
+      formData.value.email = data.email;
+      formData.value.address = data.address;
+      formData.value.description = data.memo;
+      formData.value.isEnabled = data.isEnable;
+      formData.value.processCount = data.number0fWorkFlow;
+      formData.value.pageCount = data.number0fPages;
+      formData.value.tableCount = data.number0fTables;
+      formData.value.designerCount = data.number0fDesigners;
+      formData.value.scenarioCount = data.number0fBusinessScenarios;
+      formData.value.userCount = data.number0fCSiteMaxUser;
+      formData.value.fileId = data.imgPhotoFileId;
+      formData.value.imgPhotoFileId = data.imgPhotoFileId;
+      formData.value.partner = data.partner_id;
+      formData.value.id = data.id;
+      formData.value.customerRelation = data.client_code;
+      formData.value.nameEn =
+        data.langNameList.find((item: any) => item.name === 'en')?.value || '';
+      if (data.name) {
+        formData.value.nameCn = data.name;
+      }
+
+      if (data.imgPhotoFileId) {
+        formData.value.fileList = [
+          {
+            uid: '-1',
+            name: 'logo.png',
+            status: 'done',
+            url: `/File/Download?fileId=${data.imgPhotoFileId}`,
+            response: {
+              result: [
+                {
+                  id: data.imgPhotoFileId,
+                },
+              ],
+            },
+          },
+        ];
+      }
+    }
+  } catch {}
+}
+
+async function fetchSystemSecret() {
+  try {
+    const result = await getSystemSecretApi(formData.value.code);
+    if (result?.result?.generateSystemEncryptInfo) {
+      keyData.value.startTime = result.result.generateSystemEncryptInfo
+        .begin_date
+        ? dayjs(result.result.generateSystemEncryptInfo.begin_date)
+        : null;
+      keyData.value.validDays =
+        result.result.generateSystemEncryptInfo.period_day;
+      keyData.value.isPermanent =
+        result.result.generateSystemEncryptInfo.is_permanent;
+      keyData.value.errorDescription =
+        result.result.generateSystemEncryptInfo.error_msg;
+      keyData.value.systemKey = result.result.encryptInfo ?? '';
+    }
+  } catch {}
+}
+
+async function getAssociatedUsers() {
+  try {
+    const result = await getAssociatedUsersApi(formData.value.code);
+    if (result?.result) {
+      relatedUsers.value = result.result;
+    }
+  } catch {}
+}
+
+async function getAddUsers() {
+  try {
+    const result = await getAddUsersApi({
+      currentPage: userPagination.value.currentPage,
+      pageSize: userPagination.value.pageSize,
+      orderByProperty: 'name',
+      Ascending: true,
+      filters: [
+        {
+          name: 'filterText',
+          value: userSearchKeyword.value,
+        },
+      ],
+    });
+    if (result?.result?.model) {
+      allUsers.value = result.result.model;
+      userPagination.value.total = result.result.totalCount;
+    }
+  } catch {}
+}
 
 
 watch(
 watch(
   () => props.open,
   () => props.open,
   (val) => {
   (val) => {
     if (val && props.mode === 'edit' && props.applicationData) {
     if (val && props.mode === 'edit' && props.applicationData) {
       Object.assign(formData.value, props.applicationData);
       Object.assign(formData.value, props.applicationData);
+      fetchApplicationDetail();
+      fetchSystemSecret();
+      getAssociatedUsers();
+      getAddUsers();
     }
     }
     if (val) {
     if (val) {
+      if (props.mode === 'add') {
+        resetFormData();
+      }
       fetchPartners();
       fetchPartners();
       fetchCloneApplications();
       fetchCloneApplications();
+      fetchCustomerRelations();
+      activeMenu.value = 'basic';
     }
     }
   },
   },
 );
 );
@@ -166,6 +311,7 @@ function handleLogoUpload(info: any) {
 
 
 async function handleSave() {
 async function handleSave() {
   const data = {
   const data = {
+    id: formData.value.id,
     langNameList: [
     langNameList: [
       {
       {
         name: 'zh-CN',
         name: 'zh-CN',
@@ -176,6 +322,7 @@ async function handleSave() {
         value: formData.value.nameEn,
         value: formData.value.nameEn,
       },
       },
     ],
     ],
+    client_code: formData.value.customerRelation,
     code: formData.value.projectCode,
     code: formData.value.projectCode,
     fileId: formData.value.fileId,
     fileId: formData.value.fileId,
     imgPhotoFileId: formData.value.imgPhotoFileId,
     imgPhotoFileId: formData.value.imgPhotoFileId,
@@ -195,15 +342,15 @@ async function handleSave() {
     number0fCSiteMaxUser: formData.value.userCount,
     number0fCSiteMaxUser: formData.value.userCount,
   };
   };
   try {
   try {
-    const result = await addApplicationApi(data);
+    const result = formData.value.id
+      ? await updateApplicationApi(data)
+      : await addApplicationApi(data);
     if (result?.isSuccess) {
     if (result?.isSuccess) {
       emit('save', data);
       emit('save', data);
       isOpen.value = false;
       isOpen.value = false;
       message.success('应用保存成功');
       message.success('应用保存成功');
     }
     }
-  } catch (error) {
-    console.error('保存应用失败:', error);
-  }
+  } catch {}
 }
 }
 
 
 function handleCancel() {
 function handleCancel() {
@@ -216,37 +363,132 @@ function handleAddUser() {
   selectedUsers.value = [];
   selectedUsers.value = [];
 }
 }
 
 
-function handleUserSearch() {}
+function handleUserSearch() {
+  userPagination.value.currentPage = 1;
+  getAddUsers();
+}
 
 
-function handleUserSelect(user: any) {
-  if (!selectedUsers.value.some((u) => u.id === user.id)) {
-    selectedUsers.value.push(user);
-  }
+function handleUserPageChange(pagination: any) {
+  userPagination.value.currentPage = pagination.current;
+  userPagination.value.pageSize = pagination.pageSize;
+  getAddUsers();
 }
 }
 
 
-function handleUserRemove(user: any) {
-  selectedUsers.value = selectedUsers.value.filter((u) => u.id !== user.id);
+async function handleUserSelect(user: any) {
+  try {
+    const result = await addUserToApplicationApi(
+      user.account,
+      formData.value.code,
+    );
+    if (result?.isSuccess) {
+      message.success('用户添加成功');
+      getAssociatedUsers();
+      userModalOpen.value = false;
+    }
+  } catch {}
 }
 }
 
 
-function handleUserSave() {
-  relatedUsers.value = [...relatedUsers.value, ...selectedUsers.value];
-  userModalOpen.value = false;
+async function handleUserDelete(user: any) {
+  Modal.confirm({
+    title: 'Are you sure delete this task?',
+    content: 'Some descriptions',
+    okText: 'Yes',
+    okType: 'danger',
+    cancelText: 'No',
+    async onOk() {
+      try {
+        const result = await deleteUserFromApplicationApi(user.id, [
+          formData.value.id,
+        ]);
+        if (result?.isSuccess) {
+          message.success('用户删除成功');
+          getAssociatedUsers();
+        }
+      } catch {}
+    },
+    onCancel() {},
+  });
 }
 }
 
 
-function handleUserCancel() {
-  userModalOpen.value = false;
+async function handleAssociateUser(user: any) {
+  try {
+    const data = {
+      enterpriseId: formData.value.id,
+      userId: user.id,
+      roleTypes: user.roleTypes,
+    };
+    const result = await associateUserApi(data);
+    if (result?.isSuccess) {
+      message.success('用户保存成功');
+    }
+  } catch {}
 }
 }
 
 
-function handleUserDelete(user: any) {
-  relatedUsers.value = relatedUsers.value.filter((u) => u.id !== user.id);
+async function handleGenerateKey() {
+  try {
+    const data = {
+      begin_date: keyData.value.startTime
+        ? (keyData.value.startTime as Dayjs).format('YYYY-MM-DD')
+        : '',
+      encrypt_type: 1,
+      enterprise_code: 'SL_tsex1',
+      error_msg: keyData.value.errorDescription,
+      is_permanent: keyData.value.isPermanent,
+      period_day: keyData.value.validDays,
+    };
+    const result = await generateSystemSecretApi(data);
+    if (result?.result) {
+      message.success('系统密钥生成成功');
+      fetchSystemSecret();
+    }
+  } catch {}
 }
 }
 
 
-function handleGenerateKey() {
-  keyData.value.systemKey = `generated-key-${Date.now()}`;
+function handleCopyKey() {
+  if (keyData.value.systemKey) {
+    navigator.clipboard.writeText(keyData.value.systemKey);
+    message.success('系统密钥已复制');
+  }
 }
 }
 
 
-function handleKeyCancel() {
-  activeMenu.value = 'basic';
+function resetFormData() {
+  formData.value = {
+    code: '',
+    id: '',
+    logo: null,
+    fileId: '',
+    imgPhotoFileId: '',
+    fileList: [] as any[],
+    partner: '',
+    projectCode: '',
+    cloneProject: '',
+    nameCn: '',
+    nameEn: '',
+    customerRelation: '',
+    contact: '',
+    phone: '',
+    email: '',
+    address: '',
+    description: '',
+    isEnabled: true,
+    processCount: 0,
+    pageCount: 0,
+    tableCount: 0,
+    designerCount: 0,
+    scenarioCount: 0,
+    userCount: 0,
+  };
+
+  keyData.value = {
+    startTime: null,
+    encryptionType: 'DES3',
+    validDays: 0,
+    isPermanent: false,
+    errorDescription: '',
+    systemKey: '',
+  };
+
+  relatedUsers.value = [];
 }
 }
 </script>
 </script>
 
 
@@ -274,9 +516,10 @@ function handleKeyCancel() {
             <div class="flex flex-col gap-2">
             <div class="flex flex-col gap-2">
               <label class="text-sm font-medium">企业Logo</label>
               <label class="text-sm font-medium">企业Logo</label>
               <Upload
               <Upload
+                v-model:file-list="formData.fileList"
                 :headers="{ Authorization: String(token) }"
                 :headers="{ Authorization: String(token) }"
                 :max-count="1"
                 :max-count="1"
-                action="/fileApi/File/UploadFiles"
+                action="http://a.dev.jbpm.shalu.com/fileApi/File/UploadFiles"
                 list-type="picture-card"
                 list-type="picture-card"
                 @change="handleLogoUpload"
                 @change="handleLogoUpload"
               >
               >
@@ -297,6 +540,7 @@ function handleKeyCancel() {
               <label class="text-sm font-medium">合作伙伴</label>
               <label class="text-sm font-medium">合作伙伴</label>
               <Select
               <Select
                 v-model:value="formData.partner"
                 v-model:value="formData.partner"
+                :disabled="props.mode === 'edit'"
                 :options="applicationMenu"
                 :options="applicationMenu"
                 class="h-[32px]"
                 class="h-[32px]"
                 placeholder="请选择合作伙伴"
                 placeholder="请选择合作伙伴"
@@ -306,6 +550,7 @@ function handleKeyCancel() {
               <label class="text-sm font-medium">项目代码</label>
               <label class="text-sm font-medium">项目代码</label>
               <Input
               <Input
                 v-model:value="formData.projectCode"
                 v-model:value="formData.projectCode"
+                :disabled="props.mode === 'edit'"
                 placeholder="请输入项目代码"
                 placeholder="请输入项目代码"
               />
               />
             </div>
             </div>
@@ -313,6 +558,7 @@ function handleKeyCancel() {
               <label class="text-sm font-medium">克隆项目</label>
               <label class="text-sm font-medium">克隆项目</label>
               <Select
               <Select
                 v-model:value="formData.cloneProject"
                 v-model:value="formData.cloneProject"
+                :disabled="props.mode === 'edit'"
                 :options="applications"
                 :options="applications"
                 class="h-[32px]"
                 class="h-[32px]"
                 placeholder="请选择克隆项目"
                 placeholder="请选择克隆项目"
@@ -334,9 +580,11 @@ function handleKeyCancel() {
             </div>
             </div>
             <div class="flex flex-col gap-2">
             <div class="flex flex-col gap-2">
               <label class="text-sm font-medium">客户关系</label>
               <label class="text-sm font-medium">客户关系</label>
-              <Input
+              <Select
                 v-model:value="formData.customerRelation"
                 v-model:value="formData.customerRelation"
-                placeholder="请输入客户关系"
+                :options="customerRelationsOptions"
+                class="h-[32px]"
+                placeholder="请选择客户关系"
               />
               />
             </div>
             </div>
             <div class="flex flex-col gap-2">
             <div class="flex flex-col gap-2">
@@ -411,24 +659,38 @@ function handleKeyCancel() {
           <Table
           <Table
             :columns="[
             :columns="[
               { title: '账户', dataIndex: 'account', key: 'account' },
               { title: '账户', dataIndex: 'account', key: 'account' },
-              { title: '角色', dataIndex: 'role', key: 'role' },
-              { title: '操作', key: 'action', width: 100 },
+              { title: '角色', dataIndex: 'roleTypes', key: 'roleTypes' },
+              { title: '操作', key: 'action', width: 200 },
             ]"
             ]"
             :data-source="relatedUsers"
             :data-source="relatedUsers"
             :pagination="false"
             :pagination="false"
           >
           >
             <template #bodyCell="{ column, record }">
             <template #bodyCell="{ column, record }">
-              <template v-if="column.key === 'role'">
+              <template v-if="column.key === 'roleTypes'">
                 <Select
                 <Select
-                  v-model:value="record.role"
+                  v-model:value="record.roleTypes"
                   :options="roleOptions"
                   :options="roleOptions"
                   style="width: 120px"
                   style="width: 120px"
                 />
                 />
               </template>
               </template>
               <template v-if="column.key === 'action'">
               <template v-if="column.key === 'action'">
-                <Button danger size="small" @click="handleUserDelete(record)">
-                  删除
-                </Button>
+                <div class="flex">
+                  <Button
+                    size="small"
+                    type="primary"
+                    @click="handleAssociateUser(record)"
+                  >
+                    保存
+                  </Button>
+                  <Button
+                    class="ml-[10px]"
+                    danger
+                    size="small"
+                    @click="handleUserDelete(record)"
+                  >
+                    删除
+                  </Button>
+                </div>
               </template>
               </template>
             </template>
             </template>
           </Table>
           </Table>
@@ -440,6 +702,7 @@ function handleKeyCancel() {
               <label class="text-sm font-medium">开始时间</label>
               <label class="text-sm font-medium">开始时间</label>
               <DatePicker
               <DatePicker
                 v-model:value="keyData.startTime"
                 v-model:value="keyData.startTime"
+                format="YYYY-MM-DD"
                 style="width: 100%"
                 style="width: 100%"
               />
               />
             </div>
             </div>
@@ -447,6 +710,7 @@ function handleKeyCancel() {
               <label class="text-sm font-medium">加密类型</label>
               <label class="text-sm font-medium">加密类型</label>
               <Input
               <Input
                 v-model:value="keyData.encryptionType"
                 v-model:value="keyData.encryptionType"
+                disabled
                 placeholder="请输入加密类型"
                 placeholder="请输入加密类型"
               />
               />
             </div>
             </div>
@@ -479,14 +743,23 @@ function handleKeyCancel() {
             </div>
             </div>
           </div>
           </div>
           <div class="mt-6 flex justify-end gap-2">
           <div class="mt-6 flex justify-end gap-2">
-            <Button @click="handleKeyCancel">取消</Button>
             <Button type="primary" @click="handleGenerateKey">生成</Button>
             <Button type="primary" @click="handleGenerateKey">生成</Button>
+            <Button
+              v-if="keyData.systemKey"
+              type="primary"
+              @click="handleCopyKey"
+            >
+              复制
+            </Button>
           </div>
           </div>
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
 
 
-    <div class="flex justify-end gap-2 border-t pt-4">
+    <div
+      v-if="activeMenu === 'basic'"
+      class="flex justify-end gap-2 border-t pt-4"
+    >
       <Button @click="handleCancel">取消</Button>
       <Button @click="handleCancel">取消</Button>
       <Button type="primary" @click="handleSave">保存</Button>
       <Button type="primary" @click="handleSave">保存</Button>
     </div>
     </div>
@@ -495,7 +768,7 @@ function handleKeyCancel() {
       v-model:open="userModalOpen"
       v-model:open="userModalOpen"
       :footer="null"
       :footer="null"
       title="添加用户"
       title="添加用户"
-      width="600px"
+      width="670px"
     >
     >
       <div class="space-y-4">
       <div class="space-y-4">
         <Input
         <Input
@@ -503,33 +776,48 @@ function handleKeyCancel() {
           placeholder="搜索用户"
           placeholder="搜索用户"
           @change="handleUserSearch"
           @change="handleUserSearch"
         />
         />
-        <div class="h-[300px] overflow-y-auto rounded border p-2">
-          <div
-            v-for="user in allUsers"
-            :key="user.id"
-            class="flex cursor-pointer items-center justify-between p-2 hover:bg-gray-100"
-            @click="handleUserSelect(user)"
-          >
-            <div class="flex items-center gap-2">
-              <input
-                :checked="selectedUsers.some((u) => u.id === user.id)"
-                type="checkbox"
-                @change="
-                  (e: any) =>
-                    e.target.checked
-                      ? handleUserSelect(user)
-                      : handleUserRemove(user)
-                "
-              />
-              <span>{{ user.account }}</span>
-            </div>
-          </div>
-        </div>
-        <div class="mt-4 flex justify-end gap-2">
-          <Button @click="handleUserCancel">取消</Button>
-          <Button type="primary" @click="handleUserSave">保存</Button>
-        </div>
+        <Table
+          :columns="[
+            { title: '账户', dataIndex: 'account', key: 'account' },
+            { title: '姓名', dataIndex: 'name', key: 'name' },
+            { title: '邮箱', dataIndex: 'emailAddress', key: 'emailAddress' },
+            { title: '电话号码', dataIndex: 'cellPhone', key: 'cellPhone' },
+            { title: '操作', key: 'action', width: 200 },
+          ]"
+          :data-source="allUsers"
+          :pagination="{
+            current: userPagination.currentPage,
+            pageSize: userPagination.pageSize,
+            total: userPagination.total,
+            showSizeChanger: true,
+            showTotal: (total: number) => `共 ${total} 条`,
+          }"
+          @change="handleUserPageChange"
+        >
+          <template #bodyCell="{ column, record }">
+            <template v-if="column.key === 'action'">
+              <Button
+                size="small"
+                type="primary"
+                @click="handleUserSelect(record)"
+              >
+                选择
+              </Button>
+            </template>
+          </template>
+        </Table>
       </div>
       </div>
     </Modal>
     </Modal>
   </Modal>
   </Modal>
 </template>
 </template>
+
+<style lang="scss">
+.ant-menu-light .ant-menu-item-selected,
+.ant-menu-light > .ant-menu .ant-menu-item-selected {
+  background-color: #f4f2f2;
+
+  .ant-menu-title-content {
+    color: #462424;
+  }
+}
+</style>

+ 66 - 18
apps/web-velofex/src/views/dashboard/application-management/index.vue

@@ -1,11 +1,11 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { computed, ref, watch } from 'vue';
 import { computed, ref, watch } from 'vue';
 
 
-import { VbenButton } from '@vben/common-ui';
 import { useUserStore } from '@vben/stores';
 import { useUserStore } from '@vben/stores';
 
 
 import { $t } from '@/locales';
 import { $t } from '@/locales';
 import {
 import {
+  Button,
   Dropdown,
   Dropdown,
   Empty,
   Empty,
   Input,
   Input,
@@ -92,6 +92,19 @@ function handleBack() {
   window.history.back();
   window.history.back();
 }
 }
 
 
+function handleClear() {
+  searchParams.value.applicationName = '';
+  searchParams.value.applicationId = '';
+  searchParams.value.cooperatePartner = '';
+  searchParams.value.activateNow = 'Yes';
+  handleSearch();
+}
+
+function handlePageChange(page: number) {
+  searchParams.value.currentPage = page;
+  fetchApplicationList();
+}
+
 async function fetchApplicationList() {
 async function fetchApplicationList() {
   if (!isLogin.value) {
   if (!isLogin.value) {
     return;
     return;
@@ -269,30 +282,62 @@ watch(
         </div>
         </div>
       </div>
       </div>
       <div class="ml-auto flex items-center gap-2">
       <div class="ml-auto flex items-center gap-2">
-        <VbenButton
-          class="flex h-[42px] w-[102px] items-center gap-[7px] rounded-[25px] border-[#000] bg-transparent text-sm font-medium"
-          variant="outline"
-          @click="handleSearch"
-        >
-          <img
-            alt=""
+        <Button class="h-[42px]" @click="handleSearch">
+          <svg
             class="h-[21.5px] w-[21.5px] cursor-pointer"
             class="h-[21.5px] w-[21.5px] cursor-pointer"
-            src="@/assets/image/search.png"
-          />
+            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>
           Search
           Search
-        </VbenButton>
-        <VbenButton
-          class="flex h-[42px] w-[133px] items-center gap-[7px] rounded-[25px] text-sm font-medium text-white"
-          style="background: linear-gradient(to right, #8b0046, #460023)"
-          @click="handleAddNew"
-        >
+        </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>
+          Clear
+        </Button>
+        <Button class="h-[42px]" type="primary" @click="handleAddNew">
           <img
           <img
             alt=""
             alt=""
             class="h-[21.5px] w-[21.5px] cursor-pointer"
             class="h-[21.5px] w-[21.5px] cursor-pointer"
             src="@/assets/image/new.png"
             src="@/assets/image/new.png"
           />
           />
           Add New
           Add New
-        </VbenButton>
+        </Button>
       </div>
       </div>
     </div>
     </div>
 
 
@@ -335,11 +380,14 @@ watch(
       </div>
       </div>
     </div>
     </div>
 
 
-    <div v-if="totalCount > 16" class="list-pagination">
+    <div class="list-pagination">
       <Pagination
       <Pagination
         v-model:current="searchParams.currentPage"
         v-model:current="searchParams.currentPage"
+        :hide-on-single-page="true"
+        :page-size="16"
         :show-size-changer="false"
         :show-size-changer="false"
         :total="totalCount"
         :total="totalCount"
+        @change="handlePageChange"
       />
       />
     </div>
     </div>
 
 

+ 1 - 1
apps/web-velofex/src/views/dashboard/home/application-management.vue

@@ -25,7 +25,7 @@ async function fetchApplicationList() {
     loading.value = true;
     loading.value = true;
     const result = await getMyApplicationListApi({
     const result = await getMyApplicationListApi({
       currentPage: 1,
       currentPage: 1,
-      pageSize: 16,
+      pageSize: 10,
       orderByProperty: 'name',
       orderByProperty: 'name',
       Ascending: true,
       Ascending: true,
       totalPage: 1,
       totalPage: 1,