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 { export interface SwitchProps {
switchTooltip?: string; // switchTooltip?: string; //
switchName?: string; // switchName?: string; //
enable: boolean | undefined; // enable?: boolean | undefined; //
showSwitch: boolean; // showSwitch: boolean; //
} }

View File

@ -178,7 +178,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, Ref, onBeforeMount } from 'vue'; 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 { IconCompass, IconQuestionCircle, IconFile, IconInfoCircle } from '@arco-design/web-vue/es/icon';
// import { Message } from '@arco-design/web-vue'; // import { Message } from '@arco-design/web-vue';
// import { useFullscreen } from '@vueuse/core'; // import { useFullscreen } from '@vueuse/core';
@ -204,6 +204,7 @@
const appStore = useAppStore(); const appStore = useAppStore();
// const { logout } = useUser(); // const { logout } = useUser();
const route = useRoute(); const route = useRoute();
const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const projectList: Ref<ProjectListItem[]> = ref([]); const projectList: Ref<ProjectListItem[]> = ref([]);
@ -227,6 +228,13 @@
value: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[] value: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
) { ) {
appStore.setCurrentProjectId(value as string); appStore.setCurrentProjectId(value as string);
router.replace({
path: route.path,
query: {
organizationId: appStore.currentOrgId,
projectId: appStore.currentProjectId,
},
});
} }
const helpCenterList = [ 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" :close="closeHandler"
:confirm="confirmHandler" :confirm="confirmHandler"
:switch-props="{ :switch-props="{
enable: isEnable, showSwitch: false,
switchName: t('project.basicInfo.status'),
switchTooltip: t('project.basicInfo.createTip'),
showSwitch: true,
}" }"
> >
<div class="form"> <div class="form">
<a-form ref="memberFormRef" :model="form" layout="vertical"> <a-form ref="projectFormRef" :model="form" layout="vertical">
<a-form-item <a-form-item
field="name" field="name"
:label="t('project.basicInfo.projectName')" :label="t('project.basicInfo.projectName')"
asterisk-position="end" asterisk-position="end"
:rules="[{ required: true, message: t('project.basicInfo.projectNameTip') }]" :rules="[{ required: true, message: t('project.basicInfo.projectNameTip') }]"
> >
<a-input v-model="form.name" allow-clear /> <a-input v-model="form.name" allow-clear :max-length="250" />
</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-form-item> </a-form-item>
<a-form-item field="description" :label="t('project.basicInfo.Description')" asterisk-position="end"> <a-form-item field="description" :label="t('project.basicInfo.Description')" asterisk-position="end">
<a-textarea v-model="form.description" allow-clear auto-size /> <a-textarea v-model="form.description" allow-clear auto-size />
@ -41,37 +28,76 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref, watch } from 'vue';
import MsDialog from '@/components/pure/ms-dialog/index.vue'; import MsDialog from '@/components/pure/ms-dialog/index.vue';
import { useI18n } from '@/hooks/useI18n'; 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 { t } = useI18n();
const emits = defineEmits<{
(e: 'update:visible', visible: boolean): void;
(e: 'success'): void;
}>();
const initForm = { const initForm = {
organizationId: '',
name: '', name: '',
userRoleIds: [],
description: '', description: '',
enable: false,
moduleIds: [], //
id: '',
userIds: [], //
}; };
const form = ref({ ...initForm }); const form = ref<UpdateProject>({ ...initForm });
const updateVisible = ref<boolean>(false); const updateVisible = ref<boolean>(false);
const projectFormRef = ref<FormInstance | null>(null);
const isEnable = ref<boolean>(false);
const confirmHandler = (enable: boolean | undefined) => {
console.log(enable);
};
const closeHandler = () => { const closeHandler = () => {
projectFormRef.value?.resetFields();
updateVisible.value = false; updateVisible.value = false;
form.value = { ...initForm };
}; };
const userGroupOptions = ref([ const confirmHandler = async () => {
{ await projectFormRef.value?.validate().then(async (error) => {
name: '', if (!error) {
id: '', 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> </script>
<style scoped></style> <style scoped></style>

View File

@ -1,132 +1,94 @@
<template> <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> <a-alert type="error">{{ t('project.basicInfo.alertDescription') }}</a-alert>
</div> </div>
<div class="wrapper mb-6 flex justify-between"> <div class="wrapper mb-6 flex justify-between">
<span class="font-medium text-[var(--color-text-000)]">{{ t('project.basicInfo.basicInfo') }}</span> <span class="font-medium text-[var(--color-text-000)]">{{ t('project.basicInfo.basicInfo') }}</span>
<MsTableMoreAction :list="tableActions" @select="handleSelect($event)"> <a-button v-if="!projectDetail?.deleted" type="outline" @click="editHandler">{{
<a-button type="outline">{{ t('project.basicInfo.action') }}</a-button> t('project.basicInfo.edit')
</MsTableMoreAction> }}</a-button>
</div> </div>
<div class="project-info mb-6 h-[112px] bg-white p-1"> <div class="project-info mb-6 h-[112px] bg-white p-1">
<div class="inner-wrapper rounded-md p-4"> <div class="inner-wrapper rounded-md p-4">
<div class="detail-info flex flex-col justify-between rounded-md p-4"> <div class="detail-info flex flex-col justify-between rounded-md p-4">
<div class="flex items-center"> <div class="flex items-center">
<span class="mr-1 font-medium text-[var(--color-text-000)]">具体的项目名称</span> <span class="one-line-text mr-1 max-w-[300px] font-medium text-[var(--color-text-000)]">{{
<span v-if="!isDelete" class="button enable-button mr-1">{{ t('project.basicInfo.enable') }}</span> projectDetail?.name
<span v-else class="button delete-button">{{ t('project.basicInfo.deleted') }}</span> }}</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>
<div class="one-line-text text-xs text-[--color-text-4]" <div class="one-line-text text-xs text-[--color-text-4]">{{ projectDetail?.description }}</div>
>描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述</div
>
</div> </div>
</div> </div>
</div> </div>
<div class="ml-1 flex flex-col"> <div class="ml-1 flex flex-col">
<div class="label-item"> <div class="label-item">
<span class="label">{{ t('project.basicInfo.createBy') }}</span> <span class="label">{{ t('project.basicInfo.createBy') }}</span>
<span>罗老师</span> <span>{{ projectDetail?.createUser }}</span>
</div> </div>
<div class="label-item"> <div class="label-item">
<span class="label">{{ t('project.basicInfo.organization') }}</span> <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>
<div class="label-item"> <div class="label-item">
<span class="label">{{ t('project.basicInfo.createTime') }}</span> <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>
</div> </div>
<UpdateProjectModal v-model:visible="isVisible" /> <UpdateProjectModal ref="projectDetailRef" v-model:visible="isVisible" @success="getProjectDetail()" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref, onBeforeMount } 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 { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import UpdateProjectModal from './components/updateProjectModal.vue'; import UpdateProjectModal from './components/updateProjectModal.vue';
import MsTag from '@/components/pure/ms-tag/ms-tag.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 { t } = useI18n();
const { openModal } = useModal(); const appStore = useAppStore();
const tableActions: ActionsItem[] = [ const emits = defineEmits<{
{ (e: 'updateLoading', loading: boolean): void;
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 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 isVisible = ref<boolean>(false);
const projectDetailRef = ref();
const editHandler = () => { const editHandler = () => {
isVisible.value = true; isVisible.value = true;
projectDetailRef.value.editProject(projectDetail.value);
}; };
const finishHandler = () => { onBeforeMount(async () => {
openModal({ getProjectDetail();
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;
}
}
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@ -139,7 +101,7 @@
.detail-info { .detail-info {
height: 100%; height: 100%;
background: url('@/assets/images/basic_bg.png'); background: url('@/assets/images/basic_bg.png');
background-size: cover; background-size: auto;
.button { .button {
border-radius: 2px; border-radius: 2px;
@apply inline-block px-2 py-1 text-xs; @apply inline-block px-2 py-1 text-xs;
@ -148,6 +110,10 @@
color: rgb(var(--success-5)); color: rgb(var(--success-5));
background: rgb(var(--success-1)); background: rgb(var(--success-1));
} }
.disabled-button {
color: var(--color-text-4);
background: var(--color-text-n8);
}
.delete-button { .delete-button {
color: rgb(var(--danger-5)); color: rgb(var(--danger-5));
background: rgb(var(--danger-1)); background: rgb(var(--danger-1));

View File

@ -1,29 +1,20 @@
export default { export default {
'project.basicInfo.edit': 'Edit', 'project.basicInfo.edit': 'Edit',
'project.basicInfo.finish': 'Finished',
'project.basicInfo.delete': 'Delete', 'project.basicInfo.delete': 'Delete',
'project.basicInfo.basicInfo': 'Basic Info', 'project.basicInfo.basicInfo': 'Basic Info',
'project.basicInfo.action': 'Action', 'project.basicInfo.action': 'Action',
'project.basicInfo.createBy': 'Created By', 'project.basicInfo.createBy': 'Created By',
'project.basicInfo.organization': 'Organization', 'project.basicInfo.organization': 'Organization',
'project.basicInfo.resourcePool': 'Resource Pool',
'project.basicInfo.createTime': 'Created Time', 'project.basicInfo.createTime': 'Created Time',
'project.basicInfo.enable': 'Enable', 'project.basicInfo.enable': 'Enable',
'project.basicInfo.disabled': 'Disabled',
'project.basicInfo.deleted': 'Deleted', 'project.basicInfo.deleted': 'Deleted',
'project.basicInfo.updateProjectTitle': 'update Project', 'project.basicInfo.updateProjectTitle': 'update Project',
'project.basicInfo.status': 'Status', '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.projectName': 'Project Name',
'project.basicInfo.projectNameTip': 'The project name cannot be empty', 'project.basicInfo.projectNameTip': 'The project name cannot be empty',
'project.basicInfo.Description': 'Description', 'project.basicInfo.Description': 'Description',
'project.basicInfo.alertDescription': 'project.basicInfo.alertDescription':
'The organization will be deleted 30 days later. To cancel the deletion, contact the administrator', 'The organization will be deleted 30 days later. To cancel the deletion, contact the administrator',
'project.basicInfo.selectOrganization': 'Please select the organization', 'project.basicInfo.updateContentTip': 'Update successfully!',
'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',
}; };

View File

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

View File

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