import { defineStore } from 'pinia' import { computed, ref } from 'vue' import { ElMessage } from 'element-plus' import type { BizTypeOption, Demand, DemandAttachment, DemandDraftVo, DictOption, } from '@/types/business/demand' import { getBizTypeDict, getBudgetRangeDict, getDraftDemand, getOssUrls, getSubmissionBizTypes, resetDraft as resetDraftApi, saveDraft as saveDraftApi, submitDemand, uploadDemandFile, } from '@/api/demand' import { getUserProfile } from '@/api/user' import { toAssetUrl } from '@/utils/asset' import { toBizTypeOption, toBizTypeOptionFromDict } from '@/utils/bizTypeMeta' import { isManualBudget, resolveDraftBudget } from '@/utils/budgetMeta' import { formatDate } from '@/utils/format' function createDefaultForm(): Demand { return { id: null, bizType: '', projectName: '', requirementDesc: '', budgetRange: '', budgetManual: false, developmentCycle: '', supplementaryRemarks: '', userName: '', userPhone: '', attachmentInfo: [], } } function sortDict(list: DictOption[] | null): DictOption[] { return [...(list ?? [])].sort((a, b) => (a.dictSort ?? 0) - (b.dictSort ?? 0)) } function mergeDraft(base: Demand, draft: DemandDraftVo, budgetDict: DictOption[]): Demand { const budget = resolveDraftBudget(draft.budgetRange, budgetDict) return { id: draft.id ?? null, bizType: draft.bizType || base.bizType, projectName: draft.projectName || base.projectName, requirementDesc: draft.requirementDesc || base.requirementDesc, budgetRange: budget.value, budgetManual: budget.manual, developmentCycle: draft.developmentCycle ?? base.developmentCycle, supplementaryRemarks: draft.supplementaryRemarks || base.supplementaryRemarks, userName: draft.userName || base.userName, userPhone: draft.userPhone || base.userPhone, attachmentInfo: draft.attachmentInfo ?? [], } } async function loadOssUrlMap(raws: (string | null)[]): Promise> { const ids = [...new Set( raws.map((item) => (item ?? '').trim()).filter((item) => item && !toAssetUrl(item)), )] if (ids.length === 0) return {} try { const list = await getOssUrls(ids) return Object.fromEntries(list.map((item) => [String(item.ossId), item.url])) } catch { return {} } } export const useDemandStore = defineStore('demand', () => { const form = ref(createDefaultForm()) const bizTypeOptions = ref([]) const budgetDict = ref([]) const loading = ref(false) const saving = ref(false) const submitting = ref(false) const uploading = ref(false) const budgetOptions = computed(() => { const list = budgetDict.value return [ ...list.filter((item) => !isManualBudget(item.dictLabel)), ...list.filter((item) => isManualBudget(item.dictLabel)), ] }) async function loadBizTypeOptions(): Promise { try { const submissions = await getSubmissionBizTypes() if (submissions.length > 0) { const ossUrlById = await loadOssUrlMap(submissions.map((item) => item.logoUrl)) const options = submissions .map((item) => toBizTypeOption(item, ossUrlById)) .filter((item): item is BizTypeOption => item !== null) if (options.length > 0) return options } } catch { } const dict = sortDict(await getBizTypeDict()) return dict.map((item) => toBizTypeOptionFromDict(item.dictValue, item.dictLabel)) } function patchForm(partial: Partial): void { form.value = { ...form.value, ...partial } } async function init(): Promise { loading.value = true try { const [bizTypes, budgetDictRes, profile, draft] = await Promise.all([ loadBizTypeOptions(), getBudgetRangeDict(), getUserProfile().catch(() => null), getDraftDemand(), ]) bizTypeOptions.value = bizTypes budgetDict.value = sortDict(budgetDictRes) const base = createDefaultForm() if (profile) { base.userName = profile.nickName || profile.userName || '' base.userPhone = profile.phoneNumber || '' } form.value = draft ? mergeDraft(base, draft, budgetDict.value) : base } catch (error) { ElMessage.error(error instanceof Error ? error.message : '需求提报数据加载失败') } finally { loading.value = false } } async function addAttachment(file: File): Promise { if (uploading.value) return uploading.value = true try { const oss = await uploadDemandFile(file) const suffix = (oss.fileSuffix || file.name.split('.').pop() || '').replace(/^\./, '') const item: DemandAttachment = { ossId: oss.ossId, originalName: oss.originalName || file.name, url: oss.url, fileSize: file.size, type: suffix, createTime: formatDate(new Date()), } form.value.attachmentInfo = [...form.value.attachmentInfo, item] ElMessage.success(`「${item.originalName}」上传成功`) } catch (error) { ElMessage.error(error instanceof Error ? error.message : '附件上传失败') } finally { uploading.value = false } } function removeAttachment(target: DemandAttachment): void { form.value.attachmentInfo = form.value.attachmentInfo.filter( (item) => !(item.ossId === target.ossId && item.id === target.id), ) } async function saveDraft(): Promise { saving.value = true try { await saveDraftApi(form.value) ElMessage.success('草稿已保存') } catch (error) { ElMessage.error(error instanceof Error ? error.message : '草稿保存失败') } finally { saving.value = false } } async function reset(): Promise { saving.value = true try { await resetDraftApi() const base = createDefaultForm() base.userName = form.value.userName base.userPhone = form.value.userPhone form.value = base ElMessage.success('已重置') } catch (error) { ElMessage.error(error instanceof Error ? error.message : '重置失败') } finally { saving.value = false } } async function submit(): Promise { if (!form.value.bizType) { ElMessage.warning('请选择业务类型') return false } if (!form.value.projectName.trim()) { ElMessage.warning('请填写项目名称') return false } if (!form.value.requirementDesc.trim()) { ElMessage.warning('请填写核心需求描述') return false } if (form.value.budgetManual && !form.value.budgetRange.trim()) { ElMessage.warning('请填写预算金额,或改选一个预设档位') return false } submitting.value = true try { await submitDemand(form.value) const base = createDefaultForm() base.userName = form.value.userName base.userPhone = form.value.userPhone form.value = base ElMessage.success('提交成功,专业顾问将在一个工作日内与您联系') return true } catch (error) { ElMessage.error(error instanceof Error ? error.message : '提交失败,请稍后重试') return false } finally { submitting.value = false } } return { form, bizTypeOptions, budgetOptions, loading, saving, submitting, uploading, init, patchForm, addAttachment, removeAttachment, saveDraft, reset, submit, } })