feat(系统设置): 添加项目管理员必填&用户选择器替换为MsSelect

This commit is contained in:
RubyLiu 2024-02-07 15:57:31 +08:00 committed by wxg0103
parent 5662e5abe7
commit ed9b95eba1
7 changed files with 104 additions and 132 deletions

View File

@ -11,7 +11,7 @@
</div>
<div v-else>
<div class="flex flex-row items-center">
<MsUserSelector v-bind="$attrs" v-model="memberList" class="w-[262px]" />
<MsUserSelector v-bind="$attrs" v-model="memberList" class="min-w-[262px]" />
<a-button
type="outline"
:disabled="!memberList.length"
@ -22,12 +22,14 @@
>
{{ t('common.confirm') }}
</a-button>
<div
class="ml-[8px] flex h-[24px] w-[48px] cursor-pointer items-center justify-center rounded-[4px] border border-[var(--color-text-input-border)] px-[11px] text-[12px] leading-[16px] text-[var(--color-text-1)] hover:bg-[rgb(var(--primary-1))]"
<a-button
type="outline"
size="mini"
class="ml-[12px] !border-[var(--color-text-input-border)] !text-[var(--color-text-1)]"
@click="handleCancel"
>
{{ t('common.cancel') }}
</div>
</a-button>
</div>
</div>
</template>

View File

@ -1,36 +1,35 @@
<template>
<a-select
:model-value="currentValue"
:placeholder="t(props.placeholder || 'common.pleaseSelectMember')"
<MsSelect
v-model:model-value="currentValue"
mode="remote"
:options="[]"
:placeholder="props.placeholder || 'common.pleaseSelectMember'"
multiple
:value-key="props.valueKey"
:disabled="props.disabled"
:at-least-one="props.atLeastOne"
:label-key="props.firstLabelKey"
:filter-option="false"
allow-clear
:loading="loading"
@change="change"
@search="debouncedSearch"
:search-keys="[props.firstLabelKey, props.secondLabelKey]"
:remote-func="loadList"
:remote-extra-params="{ ...props.loadOptionParams, type: props.type }"
:option-label-render="optionLabelRender"
:should-calculate-max-tag="false"
>
<template #label="{ data }">
<span class="text-[var(--color-text-1)]"> {{ data.value.name }} </span>
</template>
<a-option v-for="data in allOptions" :key="data.id" :disabled="data.disabled" :value="data">
<span :class="data.disabled ? 'text-[var(--color-text-4)]' : 'text-[var(--color-text-1)]'">
{{ data.name }}
</span>
<span v-if="data.email" class="text-[var(--color-text-4)]"> {{ `(${data.email})` }} </span>
</a-option>
</a-select>
</MsSelect>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { debounce } from 'lodash-es';
import { SelectOptionData } from '@arco-design/web-vue';
import { useI18n } from '@/hooks/useI18n';
import MsSelect from '@/components/business/ms-select/index';
import initOptionsFunc, { UserRequestTypeEnum } from './utils';
defineOptions({ name: 'MsUserSelector' });
export interface MsUserSelectorOption {
id: string;
name: string;
@ -41,7 +40,6 @@
const props = withDefaults(
defineProps<{
modelValue: string[] | string; //
disabled?: boolean; //
disabledKey?: string; // key
valueKey?: string; // valuekey
@ -50,6 +48,7 @@
secondLabelKey?: string; // key
loadOptionParams?: Record<string, any>; //
type?: UserRequestTypeEnum; //
atLeastOne?: boolean; //
}>(),
{
disabled: false,
@ -58,27 +57,18 @@
firstLabelKey: 'name',
secondLabelKey: 'email',
type: UserRequestTypeEnum.SYSTEM_USER_GROUP,
atLeastOne: false,
}
);
const emit = defineEmits<{
(e: 'update:modelValue', value: string[]): void;
(e: 'select', value: string[]): void;
}>();
const { t } = useI18n();
const allOptions = ref<MsUserSelectorOption[]>([]);
const currentLoadParams = ref<Record<string, any>>(props.loadOptionParams || {});
const currentValue = defineModel<string[] | string>({ default: [] });
const loading = ref(true);
const currentValue = computed(() => {
return allOptions.value.filter((item) => props.modelValue.includes(item.id)) || [];
});
const loadList = async () => {
const loadList = async (params: Record<string, any>) => {
try {
loading.value = true;
const list = (await initOptionsFunc(props.type, currentLoadParams.value || {})) || [];
const { type, keyword, ...rest } = params;
const list = (await initOptionsFunc(type, { keyword, ...rest })) || [];
const { firstLabelKey, secondLabelKey, disabledKey, valueKey } = props;
list.forEach((item: MsUserSelectorOption) => {
if (firstLabelKey) {
@ -94,55 +84,16 @@
item.id = item[valueKey] as string;
}
});
allOptions.value = [...list];
return list;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
allOptions.value = [];
return [];
} finally {
loading.value = false;
}
};
const debouncedSearch = async (value: string) => {
if (!value) {
currentLoadParams.value = {
...currentLoadParams.value,
keyword: value,
};
await loadList();
} else {
const fn = debounce(
() => {
currentLoadParams.value = {
...currentLoadParams.value,
keyword: value,
};
loadList();
},
300,
{ maxWait: 1000 }
);
fn();
}
const optionLabelRender = (option: SelectOptionData) => {
return `<span class='text-[var(--color-text-1)]'>${option.name}</span><span class='text-[var(--color-text-4)] ml-[4px]'>(${option.email})</span>`;
};
const change = (
value: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
) => {
const tmpArr = Array.isArray(value) ? value : [value];
const { valueKey } = props;
emit(
'update:modelValue',
tmpArr.map((item) => item[valueKey])
);
emit(
'select',
tmpArr.map((item) => item[valueKey])
);
};
onBeforeMount(async () => {
await loadList();
});
</script>

View File

@ -194,6 +194,10 @@
import { convertToFileByBug } from '@/views/bug-management/utils';
import { convertToFile } from '@/views/case-management/caseManagementFeature/components/utils';
defineOptions({
name: 'BugDetailTab',
});
const { t } = useI18n();
const props = defineProps<{
@ -408,7 +412,7 @@
watch(
() => fileList.value,
async (val) => {
const isNewFiles = val.filter((item) => item.status === 'init').length;
const isNewFiles = val.filter((item) => item.status === 'init' || (!item.local && !item.associateId)).length;
if (val && isNewFiles) {
startUpload();
}

View File

@ -43,14 +43,20 @@
>
</a-select>
</a-form-item>
<a-form-item field="userIds" :label="t('system.project.projectAdmin')">
<a-form-item
field="userIds"
asterisk-position="end"
:rules="[{ required: true, message: t('system.project.projectAdminIsNotNull') }]"
:label="t('system.project.projectAdmin')"
>
<MsUserSelector
v-model="form.userIds"
:type="UserRequestTypeEnum.ORGANIZATION_PROJECT_ADMIN"
placeholder="system.project.projectAdminPlaceholder"
placeholder="system.project.pleaseSelectAdmin"
:load-option-params="{
organizationId: currentOrgId,
}"
:at-least-one="true"
/>
</a-form-item>
<a-form-item field="module" :label="t('system.project.moduleSetting')">
@ -115,7 +121,7 @@
import { createOrUpdateProjectByOrg, getSystemOrgOption } from '@/api/modules/setting/organizationAndProject';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { useAppStore, useUserStore } from '@/store';
import useLicenseStore from '@/store/modules/setting/license';
import { CreateOrUpdateSystemProjectParams, SystemOrgOption } from '@/models/setting/system/orgAndProject';
@ -128,7 +134,6 @@
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
currentProject?: CreateOrUpdateSystemProjectParams;
}>();
@ -137,6 +142,7 @@
const loading = ref(false);
const isEdit = computed(() => !!(props.currentProject && props.currentProject.id));
const affiliatedOrgOption = ref<SystemOrgOption[]>([]);
const userStore = useUserStore();
const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId);
const licenseStore = useLicenseStore();
@ -180,20 +186,18 @@
moduleIds: allModuleIds,
});
const currentVisible = ref(props.visible);
const currentVisible = defineModel<boolean>('visible', {
default: false,
});
const showPool = computed(() => showPoolModuleIds.some((item) => form.moduleIds?.includes(item)));
const isXpack = computed(() => {
return licenseStore.hasLicense();
});
watchEffect(() => {
currentVisible.value = props.visible;
});
const formReset = () => {
form.name = '';
form.userIds = [];
form.userIds = userStore.id ? [userStore.id] : [];
form.organizationId = currentOrgId.value;
form.description = '';
form.enable = true;
@ -252,22 +256,24 @@
console.error(error);
}
};
watchEffect(() => {
if (isEdit.value && props.currentProject) {
form.id = props.currentProject.id;
form.name = props.currentProject.name;
form.description = props.currentProject.description;
form.enable = props.currentProject.enable;
form.userIds = props.currentProject.userIds;
form.organizationId = props.currentProject.organizationId;
form.moduleIds = props.currentProject.moduleIds;
form.resourcePoolIds = props.currentProject.resourcePoolIds;
initAffiliatedOrgOption();
if (props.currentProject?.id) {
//
if (props.currentProject) {
form.id = props.currentProject.id;
form.name = props.currentProject.name;
form.description = props.currentProject.description;
form.enable = props.currentProject.enable;
form.userIds = props.currentProject.userIds;
form.organizationId = props.currentProject.organizationId;
form.moduleIds = props.currentProject.moduleIds;
form.resourcePoolIds = props.currentProject.resourcePoolIds;
}
} else {
//
formReset();
}
});
onMounted(() => {
initAffiliatedOrgOption();
});
onUnmounted(() => {
formReset();
});
</script>

View File

@ -50,11 +50,17 @@
>
</a-select>
</a-form-item>
<a-form-item field="userIds" :label="t('system.project.projectAdmin')">
<a-form-item
field="userIds"
asterisk-position="end"
:rules="[{ required: true, message: t('system.project.projectAdminIsNotNull') }]"
:label="t('system.project.projectAdmin')"
>
<MsUserSelector
v-model="form.userIds"
:type="UserRequestTypeEnum.SYSTEM_PROJECT_ADMIN"
placeholder="system.project.projectAdminPlaceholder"
placeholder="system.project.pleaseSelectAdmin"
:at-least-one="true"
/>
</a-form-item>
<a-form-item field="module" :label="t('system.project.moduleSetting')">
@ -99,7 +105,7 @@
{{ t('common.cancel') }}
</a-button>
<a-button type="primary" :loading="loading" @click="handleBeforeOk">
{{ isEdit ? t('common.confirm') : t('common.create') }}
{{ isEdit ? t('common.update') : t('common.create') }}
</a-button>
</div>
</div>
@ -119,6 +125,7 @@
import { createOrUpdateProject, getSystemOrgOption } from '@/api/modules/setting/organizationAndProject';
import { useI18n } from '@/hooks/useI18n';
import { useUserStore } from '@/store';
import useAppStore from '@/store/modules/app';
import useLicenseStore from '@/store/modules/setting/license';
@ -127,9 +134,9 @@
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
const appStore = useAppStore();
const userStore = useUserStore();
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
currentProject?: CreateOrUpdateSystemProjectParams;
}>();
@ -183,7 +190,9 @@
resourcePoolIds: [],
});
const currentVisible = ref(props.visible);
const currentVisible = defineModel<boolean>('visible', {
default: false,
});
const showPool = computed(() => showPoolModuleIds.some((item) => form.moduleIds?.includes(item)));
const isXpack = computed(() => {
@ -192,7 +201,7 @@
const formReset = () => {
form.name = '';
form.userIds = [];
form.userIds = userStore.id ? [userStore.id] : [];
form.organizationId = '';
form.description = '';
form.enable = true;
@ -256,26 +265,22 @@
}
};
watchEffect(() => {
if (props.currentProject) {
form.id = props.currentProject.id;
form.name = props.currentProject.name;
form.description = props.currentProject.description;
form.enable = props.currentProject.enable;
form.userIds = props.currentProject.userIds;
form.organizationId = props.currentProject.organizationId;
form.moduleIds = props.currentProject.moduleIds;
form.resourcePoolIds = props.currentProject.resourcePoolIds;
initAffiliatedOrgOption();
if (props.currentProject?.id) {
//
if (props.currentProject) {
form.id = props.currentProject.id;
form.name = props.currentProject.name;
form.description = props.currentProject.description;
form.enable = props.currentProject.enable;
form.userIds = props.currentProject.userIds;
form.organizationId = props.currentProject.organizationId;
form.moduleIds = props.currentProject.moduleIds;
form.resourcePoolIds = props.currentProject.resourcePoolIds;
}
} else {
//
formReset();
}
});
watch(
() => props.visible,
(val) => {
currentVisible.value = val;
if (!val) {
formReset();
} else {
initAffiliatedOrgOption();
}
}
);
</script>

View File

@ -79,4 +79,6 @@ export default {
'system.project.searchPlaceholder': 'Search by name or id',
'system.project.afterModule':
'After the module is canceled, users will be unable to access the specified module, and existing data will remain intact',
'system.project.projectAdminIsNotNull': 'Project administrator cannot be empty',
'system.project.pleaseSelectAdmin': 'Please select project administrator',
};

View File

@ -75,4 +75,6 @@ export default {
'system.project.deleteTip': '删除后,系统会在 30天 后执行删除项目 (含项目下所有业务数据),请谨慎操作!',
'system.project.searchPlaceholder': '通过ID或项目名称搜索',
'system.project.afterModule': '取消模块后用户将无法进入指定模块,已存在的数据会继续保留',
'system.project.projectAdminIsNotNull': '项目管理员不能为空',
'system.project.pleaseSelectAdmin': '请选择项目管理员',
};