feat(项目管理): 基本信息需求调整

This commit is contained in:
xinxin.wu 2023-09-20 11:43:23 +08:00 committed by 刘瑞斌
parent 03a03e80ff
commit c4994e5ad1
10 changed files with 201 additions and 144 deletions

View File

@ -0,0 +1,13 @@
import MSR from '@/api/http/index';
import { ProjectBasicInfoUrl, UpdateProjectUrl } from '@/api/requrls/project-management/basicInfo';
import type { ProjectBasicInfoModel, UpdateProject } from '@/models/projectManagement/basicInfo';
// 获取项目详情
export function getProjectInfo(id: string) {
return MSR.get<ProjectBasicInfoModel>({ url: ProjectBasicInfoUrl, params: id });
}
// 更新项目
export function updateProject(data: UpdateProject) {
return MSR.post({ url: UpdateProjectUrl, data });
}

View File

@ -0,0 +1,2 @@
export const ProjectBasicInfoUrl = '/project/get';
export const UpdateProjectUrl = '/project/update';

View File

@ -59,7 +59,7 @@
export interface SwitchProps {
switchTooltip?: string; //
switchName?: string; //
enable: boolean | undefined; //
enable?: boolean | undefined; //
showSwitch: boolean; //
}

View File

@ -178,7 +178,7 @@
<script lang="ts" setup>
import { ref, computed, Ref, onBeforeMount } from 'vue';
import { useRoute } from 'vue-router';
import { useRoute, useRouter } from 'vue-router';
import { IconCompass, IconQuestionCircle, IconFile, IconInfoCircle } from '@arco-design/web-vue/es/icon';
// import { Message } from '@arco-design/web-vue';
// import { useFullscreen } from '@vueuse/core';
@ -204,6 +204,7 @@
const appStore = useAppStore();
// const { logout } = useUser();
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const projectList: Ref<ProjectListItem[]> = ref([]);
@ -227,6 +228,13 @@
value: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
) {
appStore.setCurrentProjectId(value as string);
router.replace({
path: route.path,
query: {
organizationId: appStore.currentOrgId,
projectId: appStore.currentProjectId,
},
});
}
const helpCenterList = [

View File

@ -0,0 +1,53 @@
export interface AdminList {
id: string;
name: string;
email: string;
password: string;
enable: boolean;
createTime: string;
updateTime: number;
language: string;
lastOrganizationId: string;
phone: string;
source: string;
lastProjectId: string;
createUser: string;
updateUser: string;
deleted: boolean; // 是否删除
adminFlag: boolean; // 是否组织/项目管理员
memberFlag: boolean; // 是否组织/项目成员
checkRoleFlag: boolean; // 是否属于用户组
sourceId: string; // 资源id
}
export interface ProjectBasicInfoModel {
id: string;
num: number;
organizationId: string;
name: string;
description: string;
createTime: string;
updateTime: number;
updateUser: string;
createUser: string;
deleteTime: number;
deleted: boolean;
deleteUser: string;
enable: boolean;
moduleSetting: string; // 模块设置
memberCount: number; // 项目成员数量
organizationName: string;
adminList: AdminList[]; // 管理员
projectCreateUserIsAdmin: boolean; // 创建人是否是管理员
moduleIds: string[];
}
export interface UpdateProject {
organizationId?: string;
name: string;
description: string;
enable?: boolean;
moduleIds?: string[]; // 模块设置
id?: string;
userIds?: string[]; // 成员数
}

View File

@ -6,31 +6,18 @@
:close="closeHandler"
:confirm="confirmHandler"
:switch-props="{
enable: isEnable,
switchName: t('project.basicInfo.status'),
switchTooltip: t('project.basicInfo.createTip'),
showSwitch: true,
showSwitch: false,
}"
>
<div class="form">
<a-form ref="memberFormRef" :model="form" layout="vertical">
<a-form ref="projectFormRef" :model="form" layout="vertical">
<a-form-item
field="name"
:label="t('project.basicInfo.projectName')"
asterisk-position="end"
:rules="[{ required: true, message: t('project.basicInfo.projectNameTip') }]"
>
<a-input v-model="form.name" allow-clear />
</a-form-item>
<a-form-item field="userRoleIds" :label="t('project.basicInfo.organization')" asterisk-position="end">
<a-select
v-model="form.userRoleIds"
multiple
allow-clear
:placeholder="t('project.basicInfo.selectOrganization')"
>
<a-option v-for="item of userGroupOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option>
</a-select>
<a-input v-model="form.name" allow-clear :max-length="250" />
</a-form-item>
<a-form-item field="description" :label="t('project.basicInfo.Description')" asterisk-position="end">
<a-textarea v-model="form.description" allow-clear auto-size />
@ -41,37 +28,76 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ref, watch } from 'vue';
import MsDialog from '@/components/pure/ms-dialog/index.vue';
import { useI18n } from '@/hooks/useI18n';
import { updateProject } from '@/api/modules/project-management/basicInfo';
import { FormInstance, Message } from '@arco-design/web-vue';
import type { UpdateProject, ProjectBasicInfoModel } from '@/models/projectManagement/basicInfo';
const { t } = useI18n();
const emits = defineEmits<{
(e: 'update:visible', visible: boolean): void;
(e: 'success'): void;
}>();
const initForm = {
organizationId: '',
name: '',
userRoleIds: [],
description: '',
enable: false,
moduleIds: [], //
id: '',
userIds: [], //
};
const form = ref({ ...initForm });
const form = ref<UpdateProject>({ ...initForm });
const updateVisible = ref<boolean>(false);
const isEnable = ref<boolean>(false);
const confirmHandler = (enable: boolean | undefined) => {
console.log(enable);
};
const projectFormRef = ref<FormInstance | null>(null);
const closeHandler = () => {
projectFormRef.value?.resetFields();
updateVisible.value = false;
form.value = { ...initForm };
};
const userGroupOptions = ref([
{
name: '',
id: '',
},
]);
const confirmHandler = async () => {
await projectFormRef.value?.validate().then(async (error) => {
if (!error) {
try {
await updateProject(form.value);
Message.success(t('project.basicInfo.updateContentTip'));
emits('success');
closeHandler();
} catch (e) {
console.log(e);
}
} else {
return false;
}
});
};
const editProject = (projectItem: ProjectBasicInfoModel) => {
const { id, name, description } = projectItem;
form.value = {
id,
name,
description,
};
};
watch(
() => updateVisible.value,
(val) => {
emits('update:visible', val);
}
);
defineExpose({
editProject,
});
</script>
<style scoped></style>

View File

@ -1,132 +1,94 @@
<template>
<div v-if="isDelete" class="mb-6">
<div v-if="projectDetail?.deleted" class="mb-6">
<a-alert type="error">{{ t('project.basicInfo.alertDescription') }}</a-alert>
</div>
<div class="wrapper mb-6 flex justify-between">
<span class="font-medium text-[var(--color-text-000)]">{{ t('project.basicInfo.basicInfo') }}</span>
<MsTableMoreAction :list="tableActions" @select="handleSelect($event)">
<a-button type="outline">{{ t('project.basicInfo.action') }}</a-button>
</MsTableMoreAction>
<a-button v-if="!projectDetail?.deleted" type="outline" @click="editHandler">{{
t('project.basicInfo.edit')
}}</a-button>
</div>
<div class="project-info mb-6 h-[112px] bg-white p-1">
<div class="inner-wrapper rounded-md p-4">
<div class="detail-info flex flex-col justify-between rounded-md p-4">
<div class="flex items-center">
<span class="mr-1 font-medium text-[var(--color-text-000)]">具体的项目名称</span>
<span v-if="!isDelete" class="button enable-button mr-1">{{ t('project.basicInfo.enable') }}</span>
<span v-else class="button delete-button">{{ t('project.basicInfo.deleted') }}</span>
<span class="one-line-text mr-1 max-w-[300px] font-medium text-[var(--color-text-000)]">{{
projectDetail?.name
}}</span>
<span v-if="!projectDetail?.deleted && projectDetail?.enable" class="button enable-button mr-1">{{
t('project.basicInfo.enable')
}}</span>
<span v-if="!projectDetail?.deleted && !projectDetail?.enable" class="button disabled-button mr-1">{{
t('project.basicInfo.disabled')
}}</span>
<span v-if="projectDetail?.deleted" class="button delete-button">{{ t('project.basicInfo.deleted') }}</span>
</div>
<div class="one-line-text text-xs text-[--color-text-4]"
>描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述</div
>
<div class="one-line-text text-xs text-[--color-text-4]">{{ projectDetail?.description }}</div>
</div>
</div>
</div>
<div class="ml-1 flex flex-col">
<div class="label-item">
<span class="label">{{ t('project.basicInfo.createBy') }}</span>
<span>罗老师</span>
<span>{{ projectDetail?.createUser }}</span>
</div>
<div class="label-item">
<span class="label">{{ t('project.basicInfo.organization') }}</span>
<MsTag>疯狂的刚子疯狂的刚子疯狂的刚子疯狂的刚子疯狂的刚子疯狂的刚子</MsTag>
<MsTag>{{ projectDetail?.organizationName }}</MsTag>
</div>
<div class="label-item">
<span class="label">{{ t('project.basicInfo.resourcePool') }}</span>
<MsTag>资源池</MsTag>
</div>
<div class="label-item">
<span class="label">{{ t('project.basicInfo.createTime') }}</span>
<span>2023-04-23 15:33:23</span>
<span>{{ getTime(projectDetail?.createTime as string) }}</span>
</div>
</div>
<UpdateProjectModal v-model:visible="isVisible" />
<UpdateProjectModal ref="projectDetailRef" v-model:visible="isVisible" @success="getProjectDetail()" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import { ref, onBeforeMount } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import UpdateProjectModal from './components/updateProjectModal.vue';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { useAppStore } from '@/store';
import { getProjectInfo } from '@/api/modules/project-management/basicInfo';
import type { ProjectBasicInfoModel } from '@/models/projectManagement/basicInfo';
import { getTime } from '@/utils';
const { t } = useI18n();
const { openModal } = useModal();
const appStore = useAppStore();
const tableActions: ActionsItem[] = [
{
label: 'project.basicInfo.edit',
eventTag: 'edit',
},
{
label: 'project.basicInfo.enable',
eventTag: 'enable',
},
{
label: 'project.basicInfo.finish',
eventTag: 'finish',
},
{
isDivider: true,
},
{
label: 'project.basicInfo.delete',
eventTag: 'delete',
danger: true,
},
];
const emits = defineEmits<{
(e: 'updateLoading', loading: boolean): void;
}>();
const isDelete = ref<boolean>(true);
const projectDetail = ref<ProjectBasicInfoModel>();
const getProjectDetail = async () => {
emits('updateLoading', true);
try {
projectDetail.value = await getProjectInfo(appStore.currentProjectId);
} catch (error) {
console.log(error);
} finally {
emits('updateLoading', false);
}
};
const isVisible = ref<boolean>(false);
const projectDetailRef = ref();
const editHandler = () => {
isVisible.value = true;
projectDetailRef.value.editProject(projectDetail.value);
};
const finishHandler = () => {
openModal({
type: 'warning',
title: t('project.basicInfo.finishedProject'),
content: t('project.basicInfo.finishedProjectTip'),
okText: t('project.basicInfo.confirmFinish'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'normal',
},
onBeforeOk: async () => {},
hideCancel: false,
});
};
const deleteHandler = () => {
openModal({
type: 'error',
title: t('project.member.deleteTip', { name: '此项目' }),
content: t('project.member.deleteContentTip'),
okText: t('project.basicInfo.confirmDelete'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'normal',
},
onBeforeOk: async () => {},
hideCancel: false,
});
};
function handleSelect(item: ActionsItem) {
switch (item.eventTag) {
case 'edit':
editHandler();
break;
case 'finish':
finishHandler();
break;
case 'delete':
deleteHandler();
break;
default:
break;
}
}
onBeforeMount(async () => {
getProjectDetail();
});
</script>
<style scoped lang="less">
@ -139,7 +101,7 @@
.detail-info {
height: 100%;
background: url('@/assets/images/basic_bg.png');
background-size: cover;
background-size: auto;
.button {
border-radius: 2px;
@apply inline-block px-2 py-1 text-xs;
@ -148,6 +110,10 @@
color: rgb(var(--success-5));
background: rgb(var(--success-1));
}
.disabled-button {
color: var(--color-text-4);
background: var(--color-text-n8);
}
.delete-button {
color: rgb(var(--danger-5));
background: rgb(var(--danger-1));

View File

@ -1,29 +1,20 @@
export default {
'project.basicInfo.edit': 'Edit',
'project.basicInfo.finish': 'Finished',
'project.basicInfo.delete': 'Delete',
'project.basicInfo.basicInfo': 'Basic Info',
'project.basicInfo.action': 'Action',
'project.basicInfo.createBy': 'Created By',
'project.basicInfo.organization': 'Organization',
'project.basicInfo.resourcePool': 'Resource Pool',
'project.basicInfo.createTime': 'Created Time',
'project.basicInfo.enable': 'Enable',
'project.basicInfo.disabled': 'Disabled',
'project.basicInfo.deleted': 'Deleted',
'project.basicInfo.updateProjectTitle': 'update Project',
'project.basicInfo.status': 'Status',
'project.basicInfo.createTip': 'After the project is enabled, it will be displayed in the project switching list',
'project.basicInfo.projectName': 'Project Name',
'project.basicInfo.projectNameTip': 'The project name cannot be empty',
'project.basicInfo.Description': 'Description',
'project.basicInfo.alertDescription':
'The organization will be deleted 30 days later. To cancel the deletion, contact the administrator',
'project.basicInfo.selectOrganization': 'Please select the organization',
'project.basicInfo.finishedProject': 'Finished project',
'project.basicInfo.finishedProjectTip': 'The end of the project is not displayed in the project switch list',
'project.basicInfo.confirmFinish': 'Confirm finished',
'project.basicInfo.confirmDelete': 'Confirm',
'project.member.deleteTip': 'Are you sure you want to delete the {name} project?',
'project.member.deleteContentTip':
'After the project is deleted, the system deletes the project (including all service data) 30 days later. Exercise caution when performing this operation',
'project.basicInfo.updateContentTip': 'Update successfully!',
};

View File

@ -1,14 +1,13 @@
export default {
'project.basicInfo.edit': '编辑',
'project.basicInfo.finish': '结束',
'project.basicInfo.delete': '删除',
'project.basicInfo.basicInfo': '基本信息',
'project.basicInfo.action': '操作',
'project.basicInfo.createBy': '创建人',
'project.basicInfo.organization': '所属组织',
'project.basicInfo.resourcePool': '资源池',
'project.basicInfo.createTime': '创建时间',
'project.basicInfo.enable': '启用',
'project.basicInfo.disabled': '禁用',
'project.basicInfo.deleted': '已删除',
'project.basicInfo.updateProjectTitle': '更新项目',
'project.basicInfo.status': '状态',
@ -16,12 +15,6 @@ export default {
'project.basicInfo.projectName': '项目名称',
'project.basicInfo.projectNameTip': '项目名称不能为空',
'project.basicInfo.Description': '描述',
'project.basicInfo.alertDescription': '所属组织将于 30 日后删除,如需撤销删除,请联系管理员',
'project.basicInfo.selectOrganization': '请选择组织',
'project.basicInfo.finishedProject': '结束项目',
'project.basicInfo.finishedProjectTip': '结束后的项目不展示在项目切换列表',
'project.basicInfo.confirmFinish': '确认结束',
'project.basicInfo.confirmDelete': '确认删除',
'project.member.deleteTip': '确认删除 {name} 这个项目吗?',
'project.member.deleteContentTip': '删除后,系统会在 30天 后执行删除项目 (含项目下所有业务数据),请谨慎操作!',
'project.basicInfo.alertDescription': '所属项目将于 30 日后删除,如需撤销删除,请联系管理员',
'project.basicInfo.updateContentTip': '更新成功!',
};

View File

@ -22,8 +22,8 @@
</div>
</div>
</div>
<MsCard simple :other-width="290" :min-width="700">
<router-view></router-view>
<MsCard simple :other-width="290" :min-width="700" :loading="loading">
<router-view @update-loading="updateLoading"></router-view>
</MsCard>
</div>
</template>
@ -92,6 +92,11 @@
router.push({ name: itemName });
}
};
const loading = ref<boolean>(false);
const updateLoading = (flag: boolean) => {
loading.value = flag;
};
const setInitRoute = () => {
if (route?.name) currentKey.value = route.name as string;