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