feat(系统设置): 添加项目管理员必填&用户选择器替换为MsSelect
This commit is contained in:
parent
5662e5abe7
commit
ed9b95eba1
|
@ -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>
|
||||
|
|
|
@ -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; // value的key
|
||||
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -75,4 +75,6 @@ export default {
|
|||
'system.project.deleteTip': '删除后,系统会在 30天 后执行删除项目 (含项目下所有业务数据),请谨慎操作!',
|
||||
'system.project.searchPlaceholder': '通过ID或项目名称搜索',
|
||||
'system.project.afterModule': '取消模块后用户将无法进入指定模块,已存在的数据会继续保留',
|
||||
'system.project.projectAdminIsNotNull': '项目管理员不能为空',
|
||||
'system.project.pleaseSelectAdmin': '请选择项目管理员',
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue