展示原项目组/项目,录入新项目组/项目与原因;确认时静默预览并仍级联关联单据。 Co-authored-by: Cursor <cursoragent@cursor.com>
119 lines
3.4 KiB
Vue
119 lines
3.4 KiB
Vue
<template>
|
|
<div style="width: 100%">
|
|
<el-select
|
|
style="width: 100%"
|
|
:model-value="modelValue"
|
|
:disabled="disabled"
|
|
:loading="loading"
|
|
filterable
|
|
clearable
|
|
:placeholder="team ? '请选择项目组' : '请选择项目'"
|
|
@update:model-value="$emit('update:modelValue', $event || '')"
|
|
>
|
|
<el-option
|
|
v-if="modelValue && !displayOptions.some((o) => o.id === modelValue)"
|
|
:value="modelValue"
|
|
:label="projectName || modelValue"
|
|
disabled
|
|
/>
|
|
<el-option
|
|
v-for="option in displayOptions"
|
|
:key="option.id"
|
|
:value="option.id"
|
|
:label="
|
|
team
|
|
? option.teamName
|
|
: teamId
|
|
? option.projectName
|
|
: `${option.projectName} · ${option.teamName}`
|
|
"
|
|
:disabled="!filter && option.disabled"
|
|
/>
|
|
</el-select>
|
|
<div v-if="error" class="form-tip"
|
|
>项目选项加载失败<el-button link type="primary" @click="retry++"
|
|
><el-icon><RefreshRight /></el-icon>重试</el-button
|
|
></div
|
|
>
|
|
</div>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import { projectOptionsParams } from '@/utils/project-management'
|
|
import { RefreshRight } from '@element-plus/icons-vue'
|
|
import {
|
|
fetchProjectOptions,
|
|
bizLabels,
|
|
type BizType,
|
|
type ProjectOption
|
|
} from '@/api/project-management'
|
|
const props = defineProps<{
|
|
filter?: boolean
|
|
team?: boolean
|
|
/** When selecting projects, keep options under this team. */
|
|
teamId?: string
|
|
modelValue?: string
|
|
bizType?: BizType
|
|
disabled?: boolean
|
|
projectName?: string
|
|
}>()
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: string]
|
|
required: [value: boolean]
|
|
ready: [value: boolean]
|
|
}>()
|
|
const options = ref<ProjectOption[]>([])
|
|
const displayOptions = computed(() => {
|
|
if (props.team) {
|
|
return [
|
|
...new Map(
|
|
options.value.map((option) => [option.teamId, { ...option, id: option.teamId }])
|
|
).values()
|
|
]
|
|
}
|
|
return props.teamId
|
|
? options.value.filter((option) => option.teamId === props.teamId)
|
|
: options.value
|
|
})
|
|
const loading = ref(false)
|
|
const error = ref(false),
|
|
retry = ref(0)
|
|
let version = 0
|
|
watch(
|
|
() => [props.bizType, props.disabled, props.filter, retry.value],
|
|
async () => {
|
|
const requestVersion = ++version
|
|
if (props.disabled) {
|
|
loading.value = false
|
|
return
|
|
}
|
|
loading.value = true
|
|
error.value = false
|
|
emit('ready', false)
|
|
try {
|
|
const results = await Promise.all(
|
|
(props.bizType ? [props.bizType] : (Object.keys(bizLabels) as BizType[])).map((bizType) =>
|
|
fetchProjectOptions(projectOptionsParams(bizType, props.filter))
|
|
)
|
|
)
|
|
const result = {
|
|
list: [
|
|
...new Map(
|
|
results.flatMap((result) => result.list).map((option) => [option.id, option])
|
|
).values()
|
|
],
|
|
required: results.some((result) => result.required)
|
|
}
|
|
if (requestVersion !== version) return
|
|
options.value = result.list || []
|
|
emit('required', result.required)
|
|
emit('ready', true)
|
|
} catch {
|
|
if (requestVersion === version) error.value = true
|
|
} finally {
|
|
if (requestVersion === version) loading.value = false
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
</script>
|