按专员/经理协议单诉求,把项目归属嵌入协议信息卡片并置于协议状态上方,避免独立成卡打断填写顺序。 Co-authored-by: Cursor <cursoragent@cursor.com>
103 lines
4.7 KiB
Vue
103 lines
4.7 KiB
Vue
<template>
|
||
<component :is="embedded ? 'div' : 'section'" :class="rootClass">
|
||
<h2 v-if="!embedded">{{ filter ? '项目筛选' : '项目归属' }}<span v-if="required && !editing && !filter"> *</span></h2>
|
||
<label v-else :id="labelId">所属项目<b v-if="required && !editing && !filter">*</b></label>
|
||
<template v-if="editing">
|
||
<template v-if="embedded">
|
||
<div class="market-form-field__control market-form-field__control--text" :aria-labelledby="labelId">
|
||
{{ projectLabel(record) }}
|
||
</div>
|
||
<p class="market-form-field__tip">项目组:{{ record.teamName || '—' }};归属调整请使用 Web 项目归属维护</p>
|
||
</template>
|
||
<template v-else>
|
||
<van-cell title="所属项目" :value="projectLabel(record)" />
|
||
<van-cell title="项目组" :value="record.teamName || '—'" />
|
||
<van-cell label="归属调整请使用 Web 项目归属维护" />
|
||
</template>
|
||
</template>
|
||
<template v-else>
|
||
<div :class="embedded ? 'market-form-field__control' : undefined">
|
||
<PresignSelectField
|
||
:model-value="modelValue || ''"
|
||
:options="selectOptions"
|
||
:disabled="disabled || loading || Boolean(error)"
|
||
:loading="loading"
|
||
searchable
|
||
title="所属项目"
|
||
:placeholder="filter ? '全部项目' : '请选择所属项目'"
|
||
@update:model-value="emit('update:modelValue', $event)"
|
||
/>
|
||
</div>
|
||
<template v-if="embedded">
|
||
<p v-if="loading" class="market-form-field__tip">正在加载项目…</p>
|
||
<button v-else-if="error" type="button" class="market-form-field__tip market-form-field__tip--action" @click="load">
|
||
项目加载失败,点击重试({{ error }})
|
||
</button>
|
||
<p v-else-if="!options.length" class="market-form-field__tip">
|
||
{{ required && !filter ? '请联系管理员配置可选项目后再保存' : '当前没有可选项目' }}
|
||
</p>
|
||
</template>
|
||
<template v-else>
|
||
<van-cell v-if="loading" title="正在加载项目…" />
|
||
<van-cell v-else-if="error" :label="error" title="项目加载失败,点击重试" is-link @click="load" />
|
||
<van-cell v-else-if="!options.length" :label="required && !filter ? '请联系管理员配置可选项目后再保存' : '当前没有可选项目'" />
|
||
</template>
|
||
</template>
|
||
</component>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
||
import { fetchProjectOptions } from '../../api/market.js'
|
||
import { projectLabel, projectOptions, projectValidation } from '../project-assignment.js'
|
||
import PresignSelectField from './PresignSelectField.vue'
|
||
|
||
const props = defineProps({
|
||
modelValue: { type: String, default: '' },
|
||
bizType: { type: String, required: true },
|
||
editing: { type: Boolean, default: false },
|
||
filter: { type: Boolean, default: false },
|
||
embedded: { type: Boolean, default: false },
|
||
context: { type: Object, default: null },
|
||
disabled: { type: Boolean, default: false },
|
||
record: { type: Object, default: () => ({}) }
|
||
})
|
||
const emit = defineEmits(['update:modelValue'])
|
||
const options = ref([])
|
||
const required = ref(false)
|
||
const loading = ref(true)
|
||
const error = ref('')
|
||
const labelId = `project-assignment-${Math.random().toString(36).slice(2, 9)}`
|
||
let controller
|
||
const rootClass = computed(() => (props.embedded ? 'market-form-field' : 'market-form-section'))
|
||
const selectOptions = computed(() => [
|
||
...(!required.value || props.filter ? [{ text: props.filter ? '全部项目' : '暂不归属', value: '' }] : []),
|
||
...options.value
|
||
])
|
||
async function load() {
|
||
controller?.abort()
|
||
if (props.editing) { loading.value = false; return }
|
||
const current = new AbortController()
|
||
controller = current
|
||
loading.value = true
|
||
error.value = ''
|
||
options.value = []
|
||
try {
|
||
const data = await fetchProjectOptions(props.bizType, { signal: current.signal, filter: props.filter, context: props.context })
|
||
if (controller !== current) return
|
||
options.value = projectOptions(data)
|
||
required.value = Boolean(data.required)
|
||
} catch (cause) {
|
||
if (controller === current && cause?.name !== 'AbortError') error.value = cause?.message || '请稍后重试'
|
||
} finally {
|
||
if (controller === current) loading.value = false
|
||
}
|
||
}
|
||
watch(() => [props.bizType, props.editing], (next, previous) => {
|
||
if (previous && next[0] !== previous[0] && !props.editing) emit('update:modelValue', '')
|
||
load()
|
||
}, { immediate: true })
|
||
onBeforeUnmount(() => { controller?.abort(); controller = null })
|
||
defineExpose({ validate: () => projectValidation({ editing: props.editing || props.filter, loading: loading.value, error: error.value, required: required.value, projectId: props.modelValue }) })
|
||
</script>
|