ソースを参照

feat: 新增应用管理列表,添加应用,替换登录等接口

xiaweibo 1 週間 前
コミット
0678c1404f

+ 5 - 2
apps/web-velofex/src/api/core/auth.ts

@@ -88,7 +88,10 @@ export namespace AuthApi {
  * 登录
  */
 export async function loginApi(data: AuthApi.LoginParams) {
-  return requestClient.post<AuthApi.LoginResult>('/api/account/doLogin', data);
+  return requestClient.post<AuthApi.LoginResult>(
+    '/api/account/doLoginWithCheckCode',
+    data,
+  );
 }
 
 /**
@@ -137,5 +140,5 @@ export async function sendSmsCodeApi(data: AuthApi.SendSmsCodeParams) {
  * 创建账户
  */
 export async function createAccountApi(data: AuthApi.CreateAccountParams) {
-  return requestClient.post('/api/system/createAccount', data);
+  return requestClient.post('/api/system/registerWithPhoneCode', data);
 }

+ 83 - 0
apps/web-velofex/src/api/core/user.ts

@@ -100,6 +100,53 @@ export namespace UserApi {
     usedWorkFlow: number;
   }
 
+  export interface LangName {
+    name: string;
+    value: string;
+  }
+
+  export interface AddApplicationParams {
+    isEnable: boolean;
+    langNameList: LangName[];
+    fileId?: string;
+    imgPhotoFileId?: string;
+    version?: string;
+    partnerInfoId?: string;
+    code?: string;
+    from_code?: string;
+    number0fWorkFlow?: number;
+    usedWorkFlow?: number;
+    usedPages?: number;
+    number0fPages?: number;
+    usedTables?: number;
+    number0fTables?: number;
+    usedDesigners?: number;
+    number0fDesigners?: number;
+    number0fBusinessScenarios?: number;
+    usedBusinessScenarios?: number;
+    usedCSiteMaxUser?: number;
+    number0fCSiteMaxUser?: number;
+    langName?: null | string;
+    contact?: string;
+    phone?: string;
+    email?: string;
+    address?: string;
+    description?: string;
+    customerRelation?: string;
+    nameCn?: string;
+    nameEn?: string;
+    cloneProject?: string;
+    projectCode?: string;
+    partner?: string;
+  }
+
+  export interface AddApplicationResult {
+    isSuccess: boolean;
+    code: number;
+    result: boolean;
+    isAuthorized: boolean;
+  }
+
   export interface GetDeliveryPartnersResult {
     result: any;
     currentPage: number;
@@ -142,3 +189,39 @@ export async function getDeliveryPartnersApi(
     data,
   );
 }
+
+/**
+ * 获取合作伙伴
+ */
+export async function getPartnersApi() {
+  return requestClient.post('api/partner/AllList');
+}
+
+/**
+ * 获取可以克隆的项目
+ */
+export async function getCloneApplicationsApi(
+  data: UserApi.GetDeliveryPartnersParams,
+) {
+  return requestClient.post('/api/enterprise/myList', data);
+}
+
+/**
+ * 添加应用
+ */
+export async function addApplicationApi(data: UserApi.AddApplicationParams) {
+  return requestClient.post<UserApi.AddApplicationResult>(
+    '/api/enterprise/doCreate',
+    data,
+  );
+}
+
+/**
+ * 添加 删除应用
+ */
+export async function deleteApplicationApi(data: { id: string }) {
+  return requestClient.post<UserApi.AddApplicationResult>(
+    '/api/enterprise/doDelete',
+    data,
+  );
+}

BIN
apps/web-velofex/src/assets/image/more-tow.png


BIN
apps/web-velofex/src/assets/image/new.png


BIN
apps/web-velofex/src/assets/image/search.png


+ 3 - 2
apps/web-velofex/src/components/login/login.vue

@@ -159,6 +159,7 @@ watch(
   () => open.value,
   (val) => {
     modalApi.setState({ isOpen: val });
+    isRegister.value = false;
   },
   { immediate: true },
 );
@@ -280,8 +281,8 @@ async function handleRegisterSubmit() {
         },
       });
       if (registerRes && registerRes.isSuccess) {
-        message.success('注册成功');
-        open.value = false;
+        message.success('注册成功,请登录后使用');
+        isRegister.value = false;
       } else {
         message.error(registerRes?.error || '注册失败');
       }

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

@@ -14,6 +14,16 @@ const routes: RouteRecordRaw[] = [
       title: $t('dashboard.jumpToEnterprise'),
     },
   },
+  {
+    name: 'ApplicationManagement',
+    path: '/application-management',
+    component: () =>
+      import('#/views/dashboard/application-management/index.vue'),
+    meta: {
+      icon: 'carbon:application',
+      title: $t('dashboard.applicationManagement'),
+    },
+  },
   {
     component: LayoutA,
     meta: {

+ 535 - 0
apps/web-velofex/src/views/dashboard/application-management/application-modal.vue

@@ -0,0 +1,535 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+
+import {
+  Button,
+  DatePicker,
+  Input,
+  Menu,
+  message,
+  Modal,
+  Select,
+  Switch,
+  Table,
+  Upload,
+} from 'antdv-next';
+
+import {
+  addApplicationApi,
+  getCloneApplicationsApi,
+  getPartnersApi,
+} from '#/api';
+
+interface Props {
+  open: boolean;
+  mode: 'add' | 'edit';
+  applicationData?: any;
+}
+
+const props = defineProps<Props>();
+
+const emit = defineEmits<{
+  (e: 'save', data: any): void;
+  (e: 'update:open', value: boolean): void;
+}>();
+
+const activeMenu = ref('basic');
+
+const token = localStorage.getItem('token_a');
+
+const formData = ref({
+  logo: null,
+  fileId: '',
+  imgPhotoFileId: '',
+  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,
+});
+
+const keyData = ref({
+  startTime: null,
+  encryptionType: '',
+  validDays: 0,
+  isPermanent: false,
+  errorDescription: '',
+  systemKey: '',
+});
+
+const relatedUsers = ref<any[]>([]);
+
+const userSearchKeyword = ref('');
+const userModalOpen = ref(false);
+const selectedUsers = ref<any[]>([]);
+
+const menuItems = [
+  { key: 'basic', label: '基本' },
+  { key: 'users', label: '关联用户' },
+  { key: 'keys', label: '系统密钥' },
+];
+
+const roleOptions = [
+  { value: 'normal', label: '普通人员' },
+  { value: 'parttime', label: '兼职人员' },
+];
+
+const applicationMenu = ref<Array<{ label: string; value: string }>>([]);
+const applications = ref<Array<{ label: string; value: string }>>([]);
+
+async function fetchPartners() {
+  try {
+    const result = await getPartnersApi();
+    if (result?.result) {
+      applicationMenu.value = result.result.map(
+        (item: { id: string; name: string }) => ({
+          value: item.id,
+          label: item.name,
+        }),
+      );
+    }
+  } catch (error) {
+    console.error('获取合作伙伴失败:', error);
+  }
+}
+
+async function fetchCloneApplications() {
+  try {
+    const result = await getCloneApplicationsApi({
+      currentPage: 1,
+      pageSize: 999,
+      orderByProperty: 'name',
+      Ascending: true,
+      totalPage: 1,
+      totalCount: 2,
+      filters: [{ name: 'isEnabled', value: 1 }],
+    });
+    if (result?.result?.model) {
+      applications.value = result.result.model.map(
+        (item: { code: string; name: string }) => ({
+          value: item.code,
+          label: item.name,
+        }),
+      );
+    }
+  } catch (error) {
+    console.error('获取可以克隆的项目失败:', error);
+  }
+}
+
+const allUsers = ref<any[]>([]);
+
+watch(
+  () => props.open,
+  (val) => {
+    if (val && props.mode === 'edit' && props.applicationData) {
+      Object.assign(formData.value, props.applicationData);
+    }
+    if (val) {
+      fetchPartners();
+      fetchCloneApplications();
+    }
+  },
+);
+
+const isOpen = computed({
+  get: () => props.open,
+  set: (val) => emit('update:open', val),
+});
+
+function handleMenuClick({ key }: { key: string }) {
+  activeMenu.value = key;
+}
+
+function handleLogoUpload(info: any) {
+  if (info.file.status === 'done') {
+    formData.value.logo = info.file;
+    if (info.file.response?.result?.[0]?.id) {
+      formData.value.fileId = info.file.response.result[0].id;
+      formData.value.imgPhotoFileId = info.file.response.result[0].id;
+    }
+  }
+}
+
+async function handleSave() {
+  const data = {
+    langNameList: [
+      {
+        name: 'zh-CN',
+        value: formData.value.nameCn,
+      },
+      {
+        name: 'en',
+        value: formData.value.nameEn,
+      },
+    ],
+    code: formData.value.projectCode,
+    fileId: formData.value.fileId,
+    imgPhotoFileId: formData.value.imgPhotoFileId,
+    partnerInfoId: formData.value.partner,
+    from_code: formData.value.cloneProject,
+    contact: formData.value.contact,
+    cellPhone: formData.value.phone,
+    email: formData.value.email,
+    address: formData.value.address,
+    memo: formData.value.description,
+    isEnable: formData.value.isEnabled,
+    number0fWorkFlow: formData.value.processCount,
+    number0fPages: formData.value.pageCount,
+    number0fTables: formData.value.tableCount,
+    number0fDesigners: formData.value.designerCount,
+    number0fBusinessScenarios: formData.value.scenarioCount,
+    number0fCSiteMaxUser: formData.value.userCount,
+  };
+  try {
+    const result = await addApplicationApi(data);
+    if (result?.isSuccess) {
+      emit('save', data);
+      isOpen.value = false;
+      message.success('应用保存成功');
+    }
+  } catch (error) {
+    console.error('保存应用失败:', error);
+  }
+}
+
+function handleCancel() {
+  isOpen.value = false;
+}
+
+function handleAddUser() {
+  userModalOpen.value = true;
+  userSearchKeyword.value = '';
+  selectedUsers.value = [];
+}
+
+function handleUserSearch() {}
+
+function handleUserSelect(user: any) {
+  if (!selectedUsers.value.some((u) => u.id === user.id)) {
+    selectedUsers.value.push(user);
+  }
+}
+
+function handleUserRemove(user: any) {
+  selectedUsers.value = selectedUsers.value.filter((u) => u.id !== user.id);
+}
+
+function handleUserSave() {
+  relatedUsers.value = [...relatedUsers.value, ...selectedUsers.value];
+  userModalOpen.value = false;
+}
+
+function handleUserCancel() {
+  userModalOpen.value = false;
+}
+
+function handleUserDelete(user: any) {
+  relatedUsers.value = relatedUsers.value.filter((u) => u.id !== user.id);
+}
+
+function handleGenerateKey() {
+  keyData.value.systemKey = `generated-key-${Date.now()}`;
+}
+
+function handleKeyCancel() {
+  activeMenu.value = 'basic';
+}
+</script>
+
+<template>
+  <Modal
+    v-model:open="isOpen"
+    :footer="null"
+    :title="mode === 'add' ? '添加应用' : '编辑应用'"
+    width="1200px"
+  >
+    <div class="flex h-[600px]">
+      <div v-if="mode !== 'add'" class="w-[200px] border-r border-gray-200">
+        <Menu
+          :items="menuItems"
+          :selected-keys="[activeMenu]"
+          mode="vertical"
+          @click="handleMenuClick"
+          @update:selected-keys="(keys) => (activeMenu = keys[0] ?? 'basic')"
+        />
+      </div>
+
+      <div class="flex-1 overflow-y-auto p-6">
+        <div v-show="activeMenu === 'basic'" class="space-y-4">
+          <div class="flex items-center gap-4">
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">企业Logo</label>
+              <Upload
+                :headers="{ Authorization: String(token) }"
+                :max-count="1"
+                action="/fileApi/File/UploadFiles"
+                list-type="picture-card"
+                @change="handleLogoUpload"
+              >
+                <div
+                  class="flex h-[100px] w-[200px] items-center justify-center border-2 border-dashed"
+                >
+                  <div class="text-center">
+                    <div class="text-4xl">+</div>
+                    <div class="text-sm text-gray-500">上传Logo</div>
+                  </div>
+                </div>
+              </Upload>
+            </div>
+          </div>
+
+          <div class="grid grid-cols-2 gap-4">
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">合作伙伴</label>
+              <Select
+                v-model:value="formData.partner"
+                :options="applicationMenu"
+                class="h-[32px]"
+                placeholder="请选择合作伙伴"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">项目代码</label>
+              <Input
+                v-model:value="formData.projectCode"
+                placeholder="请输入项目代码"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">克隆项目</label>
+              <Select
+                v-model:value="formData.cloneProject"
+                :options="applications"
+                class="h-[32px]"
+                placeholder="请选择克隆项目"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">项目名称(中文)</label>
+              <Input
+                v-model:value="formData.nameCn"
+                placeholder="请输入项目名称"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">项目名称(英文)</label>
+              <Input
+                v-model:value="formData.nameEn"
+                placeholder="请输入项目名称"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">客户关系</label>
+              <Input
+                v-model:value="formData.customerRelation"
+                placeholder="请输入客户关系"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">联系人</label>
+              <Input
+                v-model:value="formData.contact"
+                placeholder="请输入联系人"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">手机号</label>
+              <Input
+                v-model:value="formData.phone"
+                placeholder="请输入手机号"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">邮箱</label>
+              <Input v-model:value="formData.email" placeholder="请输入邮箱" />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">地址</label>
+              <Input
+                v-model:value="formData.address"
+                placeholder="请输入地址"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">描述</label>
+              <Input
+                v-model:value="formData.description"
+                :rows="3"
+                placeholder="请输入描述"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">是否启用</label>
+              <Switch v-model:checked="formData.isEnabled" class="w-[40px]" />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">流程数</label>
+              <Input v-model:value="formData.processCount" type="number" />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">页面数</label>
+              <Input v-model:value="formData.pageCount" type="number" />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">数据表</label>
+              <Input v-model:value="formData.tableCount" type="number" />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">设计人员数</label>
+              <Input v-model:value="formData.designerCount" type="number" />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">业务场景数</label>
+              <Input v-model:value="formData.scenarioCount" type="number" />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">C端用户数</label>
+              <Input v-model:value="formData.userCount" type="number" />
+            </div>
+          </div>
+        </div>
+
+        <div v-show="activeMenu === 'users'" class="space-y-4">
+          <div class="mb-4 flex items-center justify-between">
+            <Button type="primary" @click="handleAddUser">添加用户</Button>
+          </div>
+
+          <Table
+            :columns="[
+              { title: '账户', dataIndex: 'account', key: 'account' },
+              { title: '角色', dataIndex: 'role', key: 'role' },
+              { title: '操作', key: 'action', width: 100 },
+            ]"
+            :data-source="relatedUsers"
+            :pagination="false"
+          >
+            <template #bodyCell="{ column, record }">
+              <template v-if="column.key === 'role'">
+                <Select
+                  v-model:value="record.role"
+                  :options="roleOptions"
+                  style="width: 120px"
+                />
+              </template>
+              <template v-if="column.key === 'action'">
+                <Button danger size="small" @click="handleUserDelete(record)">
+                  删除
+                </Button>
+              </template>
+            </template>
+          </Table>
+        </div>
+
+        <div v-show="activeMenu === 'keys'" class="space-y-4">
+          <div class="grid grid-cols-2 gap-4">
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">开始时间</label>
+              <DatePicker
+                v-model:value="keyData.startTime"
+                style="width: 100%"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">加密类型</label>
+              <Input
+                v-model:value="keyData.encryptionType"
+                placeholder="请输入加密类型"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">有效天数</label>
+              <Input
+                v-model:value="keyData.validDays"
+                placeholder="请输入有效天数"
+                type="number"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">永久有效</label>
+              <Switch v-model:checked="keyData.isPermanent" class="w-[40px]" />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">错误信息描述</label>
+              <Input
+                v-model:value="keyData.errorDescription"
+                placeholder="请输入错误信息描述"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">系统密钥</label>
+              <Input
+                v-model:value="keyData.systemKey"
+                disabled
+                placeholder="系统密钥"
+              />
+            </div>
+          </div>
+          <div class="mt-6 flex justify-end gap-2">
+            <Button @click="handleKeyCancel">取消</Button>
+            <Button type="primary" @click="handleGenerateKey">生成</Button>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="flex justify-end gap-2 border-t pt-4">
+      <Button @click="handleCancel">取消</Button>
+      <Button type="primary" @click="handleSave">保存</Button>
+    </div>
+
+    <Modal
+      v-model:open="userModalOpen"
+      :footer="null"
+      title="添加用户"
+      width="600px"
+    >
+      <div class="space-y-4">
+        <Input
+          v-model:value="userSearchKeyword"
+          placeholder="搜索用户"
+          @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>
+      </div>
+    </Modal>
+  </Modal>
+</template>

+ 365 - 0
apps/web-velofex/src/views/dashboard/application-management/index.vue

@@ -0,0 +1,365 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+
+import { VbenButton } from '@vben/common-ui';
+import { useUserStore } from '@vben/stores';
+
+import { $t } from '@/locales';
+import {
+  Dropdown,
+  Empty,
+  Input,
+  message,
+  Modal,
+  Pagination,
+  Select,
+} from 'antdv-next';
+
+import {
+  deleteApplicationApi,
+  getMyApplicationListApi,
+  getPartnersApi,
+  type UserApi,
+} from '#/api';
+
+import ApplicationModal from './application-modal.vue';
+
+const applicationMenu = ref<Array<{ label: string; value: any }>>([]);
+
+const modalOpen = ref(false);
+const modalMode = ref<'add' | 'edit'>('add');
+const currentApplication = ref<any>(null);
+
+const userStore = useUserStore();
+const isLogin = computed(() => !!userStore.userInfo);
+
+const applicationList = ref<UserApi.ApplicationModel[]>([]);
+const loading = ref(false);
+
+const searchParams = ref({
+  applicationName: '',
+  applicationId: '',
+  cooperatePartner: '',
+  activateNow: 'Yes',
+  currentPage: 1,
+  pageSize: 16,
+});
+
+const totalPage = ref(0);
+const totalCount = ref(0);
+
+function handleSearch() {
+  searchParams.value.currentPage = 1;
+  fetchApplicationList();
+}
+
+function handleAddNew() {
+  modalMode.value = 'add';
+  currentApplication.value = null;
+  modalOpen.value = true;
+}
+
+function handleEdit(item: any) {
+  modalMode.value = 'edit';
+  currentApplication.value = item;
+  modalOpen.value = true;
+}
+
+function handleModalSave() {
+  modalOpen.value = false;
+  fetchApplicationList();
+}
+
+function handleMenuClick({ key }: { key: string }, item: any) {
+  if (key === 'Edit') {
+    handleEdit(item);
+  } else if (key === 'Remove') {
+    Modal.confirm({
+      title: 'Are you sure delete this task?',
+      content: 'Some descriptions',
+      okText: 'Yes',
+      okType: 'danger',
+      cancelText: 'No',
+      onOk() {
+        deleteApplication(item);
+      },
+      onCancel() {},
+    });
+  }
+}
+
+function handleBack() {
+  window.history.back();
+}
+
+async function fetchApplicationList() {
+  if (!isLogin.value) {
+    return;
+  }
+
+  try {
+    loading.value = true;
+    const result = await getMyApplicationListApi({
+      currentPage: searchParams.value.currentPage,
+      pageSize: searchParams.value.pageSize,
+      orderByProperty: 'name',
+      Ascending: true,
+      filters: [
+        {
+          name: 'isEnable',
+          value: searchParams.value.activateNow === 'Yes' ? '1' : '0',
+        },
+        {
+          name: 'name',
+          value: searchParams.value.applicationName,
+        },
+        {
+          name: 'partnerInfoId',
+          value: searchParams.value.cooperatePartner,
+        },
+        {
+          name: 'code',
+          value: searchParams.value.applicationId,
+        },
+      ],
+    });
+    if (result?.result?.model) {
+      applicationList.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 fetchPartners() {
+  if (!isLogin.value) {
+    return;
+  }
+
+  try {
+    loading.value = true;
+    const result = await getPartnersApi();
+    if (result?.result) {
+      applicationMenu.value = result.result.map(
+        (item: { id: any; name: any }) => ({
+          value: item.id,
+          label: item.name,
+        }),
+      );
+    }
+  } catch (error) {
+    console.error('获取合作伙伴失败:', error);
+  } finally {
+    loading.value = false;
+  }
+}
+
+async function deleteApplication(item: any) {
+  if (!isLogin.value) {
+    return;
+  }
+
+  try {
+    loading.value = true;
+    const result = await deleteApplicationApi({
+      id: item.id,
+    });
+    if (result?.result) {
+      fetchApplicationList();
+      message.success('删除成功!');
+    }
+  } catch (error) {
+    console.error('删除应用失败:', error);
+  } finally {
+    loading.value = false;
+  }
+}
+
+watch(
+  () => isLogin.value,
+  (newValue) => {
+    if (newValue) {
+      fetchApplicationList();
+      fetchPartners();
+    }
+  },
+  { immediate: true },
+);
+</script>
+
+<template>
+  <div class="p-5">
+    <div class="mb-4 flex items-center justify-between">
+      <div class="text-sm text-[#462424] text-gray-500">
+        Dashboard / Application Management
+      </div>
+      <div
+        class="cursor-pointer text-[16px] font-bold text-[#462424]"
+        @click="handleBack"
+      >
+        Back
+      </div>
+    </div>
+
+    <div class="mb-[21px] mt-[30px] text-[26px] font-bold text-[#462424]">
+      {{ $t('homeModule.applicationManagement') }}
+    </div>
+
+    <div class="mb-4 flex flex-wrap items-center gap-4">
+      <div class="flex flex-col gap-1">
+        <label class="text-[11px] text-[#000]">Application Name:</label>
+        <Input
+          v-model:value="searchParams.applicationName"
+          class="h-[42px] w-[147px] rounded-[11px] border-[#707070]"
+          placeholder=""
+        />
+      </div>
+      <div class="flex flex-col gap-1">
+        <label class="text-[11px] text-[#000]">Application ID:</label>
+        <Input
+          v-model:value="searchParams.applicationId"
+          class="h-[42px] w-[89px] 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.cooperatePartner"
+          :options="applicationMenu"
+          class="h-[42px] w-[168px] rounded-[11px] border-[#707070] text-[12px]"
+          placeholder="Choose Coope Partner"
+        />
+      </div>
+      <div class="ml-[86px] flex flex-col gap-1">
+        <label class="text-[11px] text-[#000]">Activate Now:</label>
+        <div class="flex h-[42px] items-center gap-2">
+          <label
+            class="flex cursor-pointer items-center gap-2 text-sm"
+            @click="searchParams.activateNow = 'Yes'"
+          >
+            <div
+              class="flex h-[20px] w-[20px] items-center justify-center rounded-[5px] border-[1px] border-[#707070]"
+            >
+              <div
+                v-if="searchParams.activateNow === 'Yes'"
+                class="h-[14px] w-[14px] flex-shrink-0 rounded-[3px] bg-[#462424]"
+              ></div>
+            </div>
+            Yes
+          </label>
+          <label
+            class="flex cursor-pointer items-center gap-2 text-sm"
+            @click="searchParams.activateNow = 'No'"
+          >
+            <div
+              class="flex h-[20px] w-[20px] items-center justify-center rounded-[5px] border-[1px] border-[#707070]"
+            >
+              <div
+                v-if="searchParams.activateNow === 'No'"
+                class="h-[14px] w-[14px] flex-shrink-0 rounded-[3px] bg-[#462424]"
+              ></div>
+            </div>
+            No
+          </label>
+        </div>
+      </div>
+      <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=""
+            class="h-[21.5px] w-[21.5px] cursor-pointer"
+            src="@/assets/image/search.png"
+          />
+          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"
+        >
+          <img
+            alt=""
+            class="h-[21.5px] w-[21.5px] cursor-pointer"
+            src="@/assets/image/new.png"
+          />
+          Add New
+        </VbenButton>
+      </div>
+    </div>
+
+    <div v-if="loading" class="py-8 text-center">加载中...</div>
+    <div
+      v-else-if="applicationList.length === 0"
+      class="mt-[100px] py-8 text-center text-gray-500"
+    >
+      <Empty />
+    </div>
+    <div v-else class="mb-[76px] mt-[38px] flex flex-wrap gap-[18px]">
+      <div
+        v-for="item in applicationList"
+        :key="item.id"
+        class="flex h-[78px] cursor-pointer items-center gap-[25px] rounded-[11px] bg-[#fff] px-[20px] shadow-md"
+      >
+        <img
+          :src="`/File/Download?fileId=${item.imgPhotoFileId}`"
+          alt=""
+          class="h-[48px] w-auto object-contain"
+        />
+        <Dropdown
+          :menu="{
+            items: [
+              { key: 'Edit', label: 'Edit' },
+              { key: 'Remove', label: 'Remove' },
+            ],
+          }"
+          placement="bottom"
+          @menu-click="(info: any) => handleMenuClick(info, item)"
+        >
+          <div class="flex cursor-pointer items-center gap-2">
+            <img
+              alt=""
+              class="h-[19px] w-[19px] cursor-pointer"
+              src="@/assets/image/more-tow.png"
+            />
+          </div>
+        </Dropdown>
+      </div>
+    </div>
+
+    <div v-if="totalCount > 16" class="list-pagination">
+      <Pagination
+        v-model:current="searchParams.currentPage"
+        :show-size-changer="false"
+        :total="totalCount"
+      />
+    </div>
+
+    <ApplicationModal
+      v-model:open="modalOpen"
+      :application-data="currentApplication"
+      :mode="modalMode"
+      @save="handleModalSave"
+    />
+  </div>
+</template>
+
+<style lang="scss">
+.ant-pagination-item-active {
+  color: #7a003d !important;
+  background-color: #f8f2f5 !important;
+  border-color: transparent !important;
+
+  a {
+    color: #c48da8 !important;
+  }
+}
+</style>

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

@@ -1,15 +1,18 @@
 <script setup lang="ts">
 import { computed, ref, watch } from 'vue';
+import { useRouter } from 'vue-router';
 
 import { useUserStore } from '@vben/stores';
 
 import { $t } from '@/locales';
 
 import { getMyApplicationListApi, type UserApi } from '#/api';
+import { useLoginModalStore } from '#/store';
 
+const router = useRouter();
 const userStore = useUserStore();
 const isLogin = computed(() => !!userStore.userInfo);
-
+const loginModalStore = useLoginModalStore();
 const applicationList = ref<UserApi.ApplicationModel[]>([]);
 const loading = ref(false);
 
@@ -44,6 +47,14 @@ async function fetchApplicationList() {
   }
 }
 
+function handleMore() {
+  if (!isLogin.value) {
+    loginModalStore.open();
+    return;
+  }
+  router.push('/application-management');
+}
+
 watch(
   () => isLogin.value,
   (newValue) => {
@@ -68,6 +79,7 @@ watch(
         alt="more"
         class="h-[29px] w-[29px] cursor-pointer"
         src="@/assets/image/home-more.png"
+        @click="handleMore"
       />
     </div>
     <p

+ 5 - 0
apps/web-velofex/vite.config.mts

@@ -22,6 +22,11 @@ export default defineConfig(async () => {
             target: 'http://a.dev.jbpm.shalu.com/',
             ws: true,
           },
+          '/fileApi': {
+            changeOrigin: true,
+            target: 'http://a.dev.jbpm.shalu.com/',
+            ws: true,
+          },
         },
       },
     },