260 lines
7.4 KiB
TypeScript
260 lines
7.4 KiB
TypeScript
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<Record<string, string>> {
|
|
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<Demand>(createDefaultForm())
|
|
const bizTypeOptions = ref<BizTypeOption[]>([])
|
|
const budgetDict = ref<DictOption[]>([])
|
|
const loading = ref(false)
|
|
const saving = ref(false)
|
|
const submitting = ref(false)
|
|
const uploading = ref(false)
|
|
|
|
const budgetOptions = computed<DictOption[]>(() => {
|
|
const list = budgetDict.value
|
|
return [
|
|
...list.filter((item) => !isManualBudget(item.dictLabel)),
|
|
...list.filter((item) => isManualBudget(item.dictLabel)),
|
|
]
|
|
})
|
|
|
|
async function loadBizTypeOptions(): Promise<BizTypeOption[]> {
|
|
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<Demand>): void {
|
|
form.value = { ...form.value, ...partial }
|
|
}
|
|
|
|
async function init(): Promise<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<boolean> {
|
|
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,
|
|
}
|
|
})
|