Bladeren bron

feat: 新增交付伙伴弹窗,修改跳转,修改应用标题

lixuan 4 weken geleden
bovenliggende
commit
c7aa53bd59

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

@@ -1,5 +1,5 @@
 # 应用标题
-VITE_APP_TITLE=VELOFEX
+VITE_APP_TITLE=SHALU
 
 # 应用命名空间,用于缓存、store等功能的前缀,确保隔离
 VITE_APP_NAMESPACE=vben-web-antd

+ 25 - 17
apps/web-velofex/src/api/core/user.ts

@@ -417,7 +417,9 @@ export namespace UserApi {
   export interface GetProjectsResult {
     isSuccess: boolean;
     code: number;
-    result: ProjectModel[];
+    result: {
+      model: ProjectModel[];
+    };
     isAuthorized: boolean;
   }
 }
@@ -463,6 +465,13 @@ export async function resetPasswordApi(data: {
   return requestClient.post('/api/account/doResetPassword', data);
 }
 
+/**
+ * 获取合作伙伴
+ */
+export async function getPartnersApi() {
+  return requestClient.post('/api/partner/AllList');
+}
+
 /**
  * 获取合作伙伴列表
  */
@@ -658,7 +667,10 @@ export async function addUserApi(data: UserApi.AddUserParams) {
  * 更新用户
  */
 export async function updateUserApi(data: UserApi.UpdateUserParams) {
-  return requestClient.post<UserApi.UserResult>('/api/user/doUpdate', data);
+  return requestClient.post<UserApi.UserResult>(
+    '/api/user/doUpdateWithAdmin',
+    data,
+  );
 }
 
 /**
@@ -673,8 +685,17 @@ export async function deleteUserApi(data: any) {
  */
 export async function getAssociatedProjectsApi(userId: string) {
   return requestClient.post<UserApi.GetProjectsResult>(
-    '/api/user/getAssociatedProjects',
-    { userId },
+    '/api/user/listUserEnterprise',
+    {
+      filters: [
+        {
+          name: 'userId',
+          value: userId,
+        },
+      ],
+      currentPage: 1,
+      pageSize: 1000,
+    },
   );
 }
 
@@ -688,19 +709,6 @@ export async function addProjectToUserApi(projectId: string, userId: string) {
   });
 }
 
-/**
- * 从用户删除项目
- */
-export async function deleteProjectFromUserApi(
-  projectId: string,
-  userIds: string[],
-) {
-  return requestClient.post<UserApi.UserResult>('/api/user/deleteProject', {
-    projectId,
-    userIds,
-  });
-}
-
 /**
  * 获取可用项目
  */

+ 499 - 0
apps/web-velofex/src/views/dashboard/delivery-partners/delivery-partners-modal.vue

@@ -0,0 +1,499 @@
+<script setup lang="ts">
+import type { TablePaginationConfig } from 'antdv-next';
+
+import { computed, ref, watch } from 'vue';
+
+import {
+  Button,
+  DatePicker,
+  Input,
+  Menu,
+  message,
+  Modal,
+  Switch,
+  Table,
+} from 'antdv-next';
+import MD5 from 'crypto-js/md5';
+import dayjs, { Dayjs } from 'dayjs';
+import localeData from 'dayjs/plugin/localeData';
+import weekday from 'dayjs/plugin/weekday';
+
+import {
+  addProjectToUserApi,
+  addUserApi,
+  deleteUserFromApplicationApi,
+  getAssociatedProjectsApi,
+  getAvailableProjectsApi,
+  updateUserApi,
+} from '#/api';
+
+interface Props {
+  open: boolean;
+  mode: 'add' | 'edit';
+  userData?: any;
+}
+
+const props = defineProps<Props>();
+const emit = defineEmits<{
+  (e: 'save', data: any): void;
+  (e: 'update:open', value: boolean): void;
+}>();
+dayjs.extend(weekday);
+dayjs.extend(localeData);
+
+const activeMenu = ref('basic');
+
+const formData = ref({
+  id: '',
+  account: '',
+  chineseName: '',
+  englishName: '',
+  cellPhone: '',
+  emailAddress: '',
+  gogs_email: '',
+  expiredTime: null as Dayjs | null,
+  isActive: true,
+  password: '',
+});
+
+const relatedProjects = ref<any[]>([]);
+
+const projectSearchKeyword = ref('');
+const projectModalOpen = ref(false);
+const selectedProjects = ref<any[]>([]);
+
+const menuItems = computed(() => [
+  {
+    key: 'basic',
+    label: '基本',
+    title: '基本',
+  },
+  {
+    key: 'projects',
+    label: '关联项目',
+    title: '关联项目',
+  },
+]);
+
+const allProjects = ref<any[]>([]);
+const projectPagination = ref({
+  currentPage: 1,
+  pageSize: 10,
+  total: 0,
+});
+
+async function fetchAssociatedProjects() {
+  try {
+    const result = await getAssociatedProjectsApi(formData.value.id);
+    if (result?.result?.model) {
+      relatedProjects.value = result.result.model;
+    }
+  } catch {}
+}
+
+async function fetchAvailableProjects() {
+  try {
+    const result = await getAvailableProjectsApi({
+      currentPage: projectPagination.value.currentPage,
+      pageSize: projectPagination.value.pageSize,
+      orderByProperty: 'name',
+      Ascending: true,
+      filters: [
+        {
+          name: 'name',
+          value: projectSearchKeyword.value,
+        },
+      ],
+    });
+    if (result?.result?.model) {
+      allProjects.value = result.result.model;
+      projectPagination.value.total = result.result.totalCount;
+    }
+  } catch {}
+}
+
+watch(
+  () => props.open,
+  (val) => {
+    if (val && props.mode === 'edit' && props.userData) {
+      // 逐个属性赋值,避免类型错误
+      formData.value.id = props.userData.id || '';
+      formData.value.account = props.userData.account || '';
+      formData.value.chineseName = props.userData.chineseName || '';
+      formData.value.englishName = props.userData.englishName || '';
+      formData.value.cellPhone = props.userData.cellPhone || '';
+      formData.value.emailAddress = props.userData.emailAddress || '';
+      formData.value.gogs_email = props.userData.gogs_email || '';
+      formData.value.isActive = props.userData.isActive;
+      try {
+        formData.value.expiredTime = props.userData.expiredTime
+          ? dayjs(props.userData.expiredTime)
+          : null;
+      } catch {
+        formData.value.expiredTime = null;
+      }
+      fetchAssociatedProjects();
+      fetchAvailableProjects();
+    }
+    if (val) {
+      if (props.mode === 'add') {
+        resetFormData();
+      }
+      activeMenu.value = 'basic';
+    }
+  },
+);
+
+const isOpen = computed({
+  get: () => props.open,
+  set: (val) => emit('update:open', val),
+});
+
+function handleMenuClick({
+  key,
+}: {
+  domEvent: Event;
+  item: any;
+  key: string;
+  keyPath: string[];
+}) {
+  activeMenu.value = key;
+}
+
+async function handleSave() {
+  const data = {
+    id: formData.value.id,
+    langNameList: [
+      {
+        name: 'zh-CN',
+        value: formData.value.chineseName,
+      },
+      {
+        name: 'en',
+        value: formData.value.englishName,
+      },
+    ],
+    expiredTime: formData.value.expiredTime
+      ? formData.value.expiredTime.format('YYYY-MM-DD')
+      : '',
+    langName: null,
+    account: formData.value.account,
+    password:
+      props.mode === 'add' ? MD5(formData.value.password).toString() : '',
+    cellPhone: formData.value.cellPhone,
+    emailAddress: formData.value.emailAddress,
+    gogs_email: formData.value.gogs_email,
+    isActive: formData.value.isActive,
+  };
+  try {
+    const result = formData.value.id
+      ? await updateUserApi(data)
+      : await addUserApi(data);
+    if (result?.isSuccess) {
+      emit('save', data);
+      isOpen.value = false;
+      message.success('保存成功');
+    }
+  } catch {}
+}
+
+function handleCancel() {
+  isOpen.value = false;
+}
+
+function handleAddProject() {
+  projectModalOpen.value = true;
+  projectSearchKeyword.value = '';
+  selectedProjects.value = [];
+  fetchAvailableProjects();
+}
+
+function handleProjectSearch() {
+  projectPagination.value.currentPage = 1;
+  fetchAvailableProjects();
+}
+
+function handleProjectPageChange(pagination: TablePaginationConfig) {
+  if (pagination.current && pagination.pageSize) {
+    projectPagination.value.currentPage = pagination.current;
+    projectPagination.value.pageSize = pagination.pageSize;
+    fetchAvailableProjects();
+  }
+}
+
+async function handleProjectSelect(project: any) {
+  try {
+    const result = await addProjectToUserApi(project.id, formData.value.id);
+    if (result?.isSuccess) {
+      message.success('添加项目成功');
+      fetchAssociatedProjects();
+      projectModalOpen.value = false;
+    }
+  } catch {}
+}
+
+async function handleProjectDelete(project: any) {
+  Modal.confirm({
+    title: '删除确认',
+    content: '确定要删除此关联项目吗?',
+    okText: '是',
+    okType: 'danger',
+    cancelText: '否',
+    type: 'warning',
+    onOk: async () => {
+      try {
+        const result = await deleteUserFromApplicationApi(formData.value.id, [
+          project.id,
+        ]);
+        if (result?.isSuccess) {
+          message.success('删除项目成功');
+          fetchAssociatedProjects();
+        }
+      } catch {}
+    },
+    onCancel: () => {},
+  });
+}
+
+function resetFormData() {
+  formData.value = {
+    id: '',
+    account: '',
+    chineseName: '',
+    englishName: '',
+    cellPhone: '',
+    emailAddress: '',
+    gogs_email: '',
+    expiredTime: null,
+    isActive: true,
+    password: '',
+  };
+
+  relatedProjects.value = [];
+}
+</script>
+
+<template>
+  <Modal
+    v-model:open="isOpen"
+    :footer="null"
+    :title="props.mode === 'add' ? '添加用户' : '编辑用户'"
+    width="1200px"
+  >
+    <div class="flex h-[600px]">
+      <div
+        v-if="props.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="grid grid-cols-2 gap-4">
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">中文姓名</label>
+              <Input
+                v-model:value="formData.chineseName"
+                placeholder="请输入中文姓名"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">英文姓名</label>
+              <Input
+                v-model:value="formData.englishName"
+                placeholder="请输入英文姓名"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">账号</label>
+              <Input
+                v-model:value="formData.account"
+                :disabled="props.mode === 'edit'"
+                placeholder="请输入账号"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">绑定手机</label>
+              <Input
+                v-model:value="formData.cellPhone"
+                placeholder="请输入绑定手机"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">邮箱</label>
+              <Input
+                v-model:value="formData.emailAddress"
+                placeholder="请输入邮箱"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">Git账户/邮箱</label>
+              <Input
+                v-model:value="formData.gogs_email"
+                placeholder="请输入Git账户/邮箱"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">有效期</label>
+              <DatePicker
+                v-model:value="formData.expiredTime"
+                format="YYYY-MM-DD"
+                placeholder="请选择有效期"
+                style="width: 100%"
+              />
+            </div>
+            <div class="flex flex-col gap-2">
+              <label class="text-sm font-medium">是否启用</label>
+              <Switch v-model:checked="formData.isActive" class="w-[40px]" />
+            </div>
+            <div v-if="props.mode === 'add'" class="flex flex-col gap-2">
+              <label class="text-sm font-medium">密码</label>
+              <Input
+                v-model:value="formData.password"
+                placeholder="请输入密码"
+                type="password"
+              />
+            </div>
+          </div>
+        </div>
+
+        <div v-show="activeMenu === 'projects'" class="space-y-4">
+          <div class="mb-4 flex items-center justify-between">
+            <Button type="primary" @click="handleAddProject"> 添加项目 </Button>
+          </div>
+
+          <Table
+            :columns="[
+              {
+                title: '项目名称',
+                dataIndex: 'name',
+                key: 'name',
+              },
+              {
+                title: '项目代码',
+                dataIndex: 'code',
+                key: 'code',
+              },
+              {
+                title: '操作',
+                key: 'action',
+                width: 100,
+              },
+            ]"
+            :data-source="relatedProjects"
+            :pagination="false"
+            :scroll="{ y: 460 }"
+          >
+            <template #bodyCell="{ column, record }">
+              <template v-if="column.key === 'action'">
+                <Button
+                  danger
+                  size="small"
+                  @click="handleProjectDelete(record)"
+                >
+                  删除
+                </Button>
+              </template>
+            </template>
+          </Table>
+        </div>
+      </div>
+    </div>
+
+    <div
+      v-if="activeMenu === 'basic'"
+      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="projectModalOpen"
+      :footer="null"
+      title="添加项目"
+      width="700"
+    >
+      <div class="space-y-4">
+        <Input
+          v-model:value="projectSearchKeyword"
+          placeholder="搜索项目"
+          @change="handleProjectSearch"
+        />
+        <Table
+          :columns="[
+            {
+              title: '项目名称',
+              dataIndex: 'name',
+              key: 'name',
+              ellipsis: true,
+              width: 120,
+            },
+            {
+              title: '项目代码',
+              dataIndex: 'code',
+              key: 'code',
+              ellipsis: true,
+              width: 120,
+            },
+            {
+              title: '操作',
+              key: 'action',
+            },
+          ]"
+          :data-source="allProjects"
+          :pagination="{
+            current: projectPagination.currentPage,
+            pageSize: projectPagination.pageSize,
+            total: projectPagination.total,
+            showSizeChanger: true,
+            showTotal: (total: number) => `共 ${total} 个项目`,
+          }"
+          :scroll="{ y: 460 }"
+          class="project-table"
+          @change="handleProjectPageChange"
+        >
+          <template #bodyCell="{ column, record }">
+            <template v-if="column.key === 'action'">
+              <Button
+                size="small"
+                type="primary"
+                @click="handleProjectSelect(record)"
+              >
+                添加
+              </Button>
+            </template>
+          </template>
+        </Table>
+      </div>
+    </Modal>
+  </Modal>
+</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;
+  }
+}
+
+.ant-table-wrapper .ant-table-tbody > tr > th,
+.ant-table-wrapper .ant-table-tbody > tr > td {
+  padding: 8px 16px;
+}
+
+.ant-table-wrapper .ant-table-thead > tr > th,
+.ant-table-wrapper .ant-table-thead > tr > td {
+  padding: 8px 16px;
+}
+</style>

+ 24 - 27
apps/web-velofex/src/views/dashboard/delivery-partners/index.vue

@@ -132,9 +132,13 @@ async function fetchUserList() {
       userList.value = result.result.model;
       totalPage.value = result.result.totalPages;
       totalCount.value = result.result.totalCount;
+    } else {
+      userList.value = [];
+      totalPage.value = 0;
+      totalCount.value = 0;
     }
-  } catch (error) {
-    console.error('获取用户列表失败:', error);
+  } catch {
+    userList.value = [];
   } finally {
     loading.value = false;
   }
@@ -152,11 +156,7 @@ async function deleteUser(item: any) {
       fetchUserList();
       message.success('删除成功');
     }
-  } catch (error) {
-    console.error('删除用户失败:', error);
-  } finally {
-    loading.value = false;
-  }
+  } catch {}
 }
 
 function handleDelete(item: any) {
@@ -190,25 +190,10 @@ async function handleResetPasswordConfirm() {
       message.success('密码重置成功');
       resetPasswordModalOpen.value = false;
     }
-  } catch (error) {
-    console.error('重置密码失败:', error);
-    message.error('重置密码失败');
-  } finally {
-    loading.value = false;
-  }
+  } catch {}
 }
 
-watch(
-  () => isLogin.value,
-  (newValue) => {
-    if (newValue) {
-      fetchUserList();
-    }
-  },
-  { immediate: true },
-);
-
-onMounted(async () => {
+async function fetchPartnersList() {
   if (!isLogin.value) {
     return;
   }
@@ -226,9 +211,21 @@ onMounted(async () => {
         }),
       );
     }
-  } catch (error) {
-    console.error('获取合作伙伴列表失败:', error);
-  }
+  } catch {}
+}
+
+watch(
+  () => isLogin.value,
+  (newValue) => {
+    if (newValue) {
+      fetchUserList();
+    }
+  },
+  { immediate: true },
+);
+
+onMounted(() => {
+  fetchPartnersList();
 });
 </script>
 

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

@@ -7,16 +7,23 @@ import { useUserStore } from '@vben/stores';
 import { $t } from '@/locales';
 
 import { getDeliveryPartnersApi, type UserApi } from '#/api';
+import { useLoginModalStore } from '#/store';
 
 const router = useRouter();
+const loginModalStore = useLoginModalStore();
+
+const userStore = useUserStore();
+
+const isLogin = computed(() => !!userStore.userInfo);
 
 function handleNavigate() {
+  if (!isLogin.value) {
+    loginModalStore.open();
+    return;
+  }
   router.push('/delivery-partners');
 }
 
-const userStore = useUserStore();
-const isLogin = computed(() => !!userStore.userInfo);
-
 const deliveryPartnersList = ref<UserApi.DeliveryPartnerModel[]>([]);
 const loading = ref(false);
 
@@ -43,11 +50,7 @@ async function fetchDeliveryPartnersList() {
     if (result?.result?.model) {
       deliveryPartnersList.value = result.result.model;
     }
-  } catch (error) {
-    console.error('获取交付伙伴列表失败:', error);
-  } finally {
-    loading.value = false;
-  }
+  } catch {}
 }
 
 watch(