|
|
@@ -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>
|