mat/frontend/src/views/material/MaterialForm.vue

401 lines
17 KiB
Vue

<template>
<el-form
v-if="mode === 'edit'"
ref="formRef"
:model="modelValue"
label-width="120px"
>
<el-form-item label="材料名称" required>
<el-input :model-value="modelValue.name" @update:model-value="set('name', $event)" />
</el-form-item>
<el-form-item label="材料大类" required>
<el-select :model-value="modelValue.major_category" @update:model-value="set('major_category', $event)">
<el-option v-for="item in majorOptions" :key="item[0]" :label="item[1]" :value="item[0]" />
</el-select>
</el-form-item>
<el-form-item label="细分种类" required>
<el-select
:model-value="modelValue.material_category"
filterable
@update:model-value="onCategoryChange"
>
<el-option v-for="item in categoryOptions" :key="item.value" :label="item.name" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="材料子类">
<el-select
:model-value="modelValue.material_subcategory"
filterable
clearable
@update:model-value="set('material_subcategory', $event)"
>
<el-option v-for="item in subcategoryOptions" :key="item.value" :label="item.name" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="阶段">
<el-select :model-value="modelValue.stage" clearable @update:model-value="set('stage', $event)">
<el-option v-for="item in stageOptions" :key="item[0]" :label="item[1]" :value="item[0]" />
</el-select>
</el-form-item>
<el-form-item label="重要等级">
<el-select :model-value="modelValue.importance_level" clearable @update:model-value="set('importance_level', $event)">
<el-option v-for="item in importanceLevelOptions" :key="item[0]" :label="item[1]" :value="item[0]" />
</el-select>
</el-form-item>
<el-form-item label="落地项目">
<el-input :model-value="modelValue.landing_project" @update:model-value="set('landing_project', $event)" />
</el-form-item>
<el-form-item label="对接人">
<el-input :model-value="modelValue.contact_person" @update:model-value="set('contact_person', $event)" />
</el-form-item>
<el-form-item label="对接人联系方式">
<el-input :model-value="modelValue.contact_phone" @update:model-value="set('contact_phone', $event)" />
</el-form-item>
<el-form-item label="经办人">
<el-input :model-value="modelValue.handler" @update:model-value="set('handler', $event)" />
</el-form-item>
<el-form-item label="备注">
<el-input :model-value="modelValue.remark" @update:model-value="set('remark', $event)" />
</el-form-item>
<el-form-item label="规格型号">
<el-input :model-value="modelValue.spec" @update:model-value="set('spec', $event)" />
</el-form-item>
<el-form-item label="符合标准">
<el-input :model-value="modelValue.standard" @update:model-value="set('standard', $event)" />
</el-form-item>
<el-form-item label="应用场景">
<el-select :model-value="modelValue.application_scene" multiple @update:model-value="set('application_scene', $event)">
<el-option v-for="item in sceneOptions" :key="item[0]" :label="item[1]" :value="item[0]" />
</el-select>
</el-form-item>
<el-form-item label="应用说明">
<el-input :model-value="modelValue.application_desc" type="textarea" @update:model-value="set('application_desc', $event)" />
</el-form-item>
<el-form-item label="替代材料类型">
<el-select :model-value="modelValue.replace_type" clearable @update:model-value="set('replace_type', $event)">
<el-option v-for="item in replaceOptions" :key="item[0]" :label="item[1]" :value="item[0]" />
</el-select>
</el-form-item>
<el-form-item label="竞争优势">
<el-select :model-value="modelValue.advantage" multiple @update:model-value="set('advantage', $event)">
<el-option v-for="item in advantageOptions" :key="item[0]" :label="item[1]" :value="item[0]" />
</el-select>
</el-form-item>
<el-form-item label="优势说明">
<el-input :model-value="modelValue.advantage_desc" type="textarea" @update:model-value="set('advantage_desc', $event)" />
</el-form-item>
<el-form-item label="成本对比(%)">
<el-input-number
:model-value="modelValue.cost_compare"
:min="-100"
:max="100"
@update:model-value="set('cost_compare', $event)"
/>
</el-form-item>
<el-form-item label="成本说明">
<el-input :model-value="modelValue.cost_desc" type="textarea" @update:model-value="set('cost_desc', $event)" />
</el-form-item>
<el-form-item label="案例">
<el-input :model-value="modelValue.cases" type="textarea" @update:model-value="set('cases', $event)" />
</el-form-item>
<el-form-item label="宣传页">
<el-upload
class="upload"
:auto-upload="true"
:show-file-list="false"
:http-request="handleUpload"
accept="image/*"
>
<el-button :loading="uploading">{{ uploading ? '上传中...' : '选择图片' }}</el-button>
</el-upload>
<div v-if="modelValue.brochure_url" class="preview">
<img :src="modelValue.brochure_url" alt="预览" />
</div>
</el-form-item>
<el-form-item label="质量等级">
<el-select :model-value="modelValue.quality_level" clearable @update:model-value="set('quality_level', $event)">
<el-option v-for="item in starOptions" :key="item[0]" :label="item[1]" :value="item[0]" />
</el-select>
</el-form-item>
<el-form-item label="耐久等级">
<el-select :model-value="modelValue.durability_level" clearable @update:model-value="set('durability_level', $event)">
<el-option v-for="item in starOptions" :key="item[0]" :label="item[1]" :value="item[0]" />
</el-select>
</el-form-item>
<el-form-item label="环保等级">
<el-select :model-value="modelValue.eco_level" clearable @update:model-value="set('eco_level', $event)">
<el-option v-for="item in starOptions" :key="item[0]" :label="item[1]" :value="item[0]" />
</el-select>
</el-form-item>
<el-form-item label="低碳等级">
<el-select :model-value="modelValue.carbon_level" clearable @update:model-value="set('carbon_level', $event)">
<el-option v-for="item in starOptions" :key="item[0]" :label="item[1]" :value="item[0]" />
</el-select>
</el-form-item>
<el-form-item label="总评分">
<el-select :model-value="modelValue.score_level" clearable @update:model-value="set('score_level', $event)">
<el-option v-for="item in starOptions" :key="item[0]" :label="item[1]" :value="item[0]" />
</el-select>
</el-form-item>
<el-form-item label="连接方式">
<el-input :model-value="modelValue.connection_method" @update:model-value="set('connection_method', $event)" />
</el-form-item>
<el-form-item label="施工工艺">
<el-input :model-value="modelValue.construction_method" @update:model-value="set('construction_method', $event)" />
</el-form-item>
<el-form-item label="限制条件">
<el-input :model-value="modelValue.limit_condition" type="textarea" @update:model-value="set('limit_condition', $event)" />
</el-form-item>
<el-form-item v-if="isAdmin" label="供应商">
<el-select :model-value="modelValue.factory" @update:model-value="set('factory', $event)">
<el-option v-for="item in factories" :key="item.id" :label="item.short_name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="品牌" required>
<el-select
:model-value="modelValue.brand"
filterable
remote
:remote-method="searchBrandsForForm"
:loading="brandFormSearchLoading"
placeholder="请选择品牌"
@update:model-value="set('brand', $event)"
>
<el-option v-for="item in brandFormOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
<template v-else>
<el-descriptions :column="1" border>
<el-descriptions-item label="材料名称">{{ displayText(modelValue.name) }}</el-descriptions-item>
<el-descriptions-item label="材料大类">{{ displayText(modelValue.major_category_display) }}</el-descriptions-item>
<el-descriptions-item label="细分种类">{{ displayText(modelValue.material_category) }}</el-descriptions-item>
<el-descriptions-item label="材料子类">{{ displayText(modelValue.material_subcategory) }}</el-descriptions-item>
<el-descriptions-item label="阶段">{{ displayText(modelValue.stage_display) }}</el-descriptions-item>
<el-descriptions-item label="重要等级">{{ displayText(modelValue.importance_level_display) }}</el-descriptions-item>
<el-descriptions-item label="落地项目">{{ displayText(modelValue.landing_project) }}</el-descriptions-item>
<el-descriptions-item label="对接人">{{ displayText(modelValue.contact_person) }}</el-descriptions-item>
<el-descriptions-item label="对接人联系方式">{{ displayText(modelValue.contact_phone) }}</el-descriptions-item>
<el-descriptions-item label="经办人">{{ displayText(modelValue.handler) }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ displayText(modelValue.remark) }}</el-descriptions-item>
<el-descriptions-item label="规格型号">{{ displayText(modelValue.spec) }}</el-descriptions-item>
<el-descriptions-item label="符合标准">{{ displayText(modelValue.standard) }}</el-descriptions-item>
<el-descriptions-item label="应用场景">{{ displayList(modelValue.application_scene_display) }}</el-descriptions-item>
<el-descriptions-item label="应用说明">{{ displayText(modelValue.application_desc) }}</el-descriptions-item>
<el-descriptions-item label="替代材料类型">{{ displayText(modelValue.replace_type_display) }}</el-descriptions-item>
<el-descriptions-item label="竞争优势">{{ displayList(modelValue.advantage_display) }}</el-descriptions-item>
<el-descriptions-item label="优势说明">{{ displayText(modelValue.advantage_desc) }}</el-descriptions-item>
<el-descriptions-item label="成本对比">{{ formatPercent(modelValue.cost_compare) }}</el-descriptions-item>
<el-descriptions-item label="成本说明">{{ displayText(modelValue.cost_desc) }}</el-descriptions-item>
<el-descriptions-item label="案例">{{ displayText(modelValue.cases) }}</el-descriptions-item>
<el-descriptions-item label="供应商">{{ displayText(modelValue.factory_name) }}</el-descriptions-item>
<el-descriptions-item label="品牌">{{ displayText(modelValue.brand_name) }}</el-descriptions-item>
<el-descriptions-item label="质量等级">{{ formatStarLevel(modelValue.quality_level) }}</el-descriptions-item>
<el-descriptions-item label="耐久等级">{{ formatStarLevel(modelValue.durability_level) }}</el-descriptions-item>
<el-descriptions-item label="环保等级">{{ formatStarLevel(modelValue.eco_level) }}</el-descriptions-item>
<el-descriptions-item label="低碳等级">{{ formatStarLevel(modelValue.carbon_level) }}</el-descriptions-item>
<el-descriptions-item label="总评分">{{ formatStarLevel(modelValue.score_level) }}</el-descriptions-item>
<el-descriptions-item label="连接方式">{{ displayText(modelValue.connection_method) }}</el-descriptions-item>
<el-descriptions-item label="施工工艺">{{ displayText(modelValue.construction_method) }}</el-descriptions-item>
<el-descriptions-item label="限制条件">{{ displayText(modelValue.limit_condition) }}</el-descriptions-item>
</el-descriptions>
<div v-if="modelValue.brochure_url" class="brochure">
<div class="brochure-title">宣传页</div>
<img :src="modelValue.brochure_url" alt="宣传页" />
</div>
</template>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { useAuth } from '@/store/auth'
import { fetchMaterialChoices, uploadImage } from '@/api/material'
import { fetchCategories, fetchSubcategories } from '@/api/category'
import { fetchFactorySimple } from '@/api/factory'
import { fetchBrands } from '@/api/brand'
const props = defineProps({
modelValue: { type: Object, required: true },
mode: { type: String, default: 'edit' }
})
const emit = defineEmits(['update:modelValue'])
const { isAdmin } = useAuth()
const formRef = ref(null)
const uploading = ref(false)
const majorOptions = ref([])
const stageOptions = ref([])
const importanceLevelOptions = ref([])
const replaceOptions = ref([])
const advantageOptions = ref([])
const sceneOptions = ref([])
const starOptions = ref([])
const categoryOptions = ref([])
const subcategoryOptions = ref([])
const factories = ref([])
const brandFormOptions = ref([])
const brandFormSearchLoading = ref(false)
const set = (key, value) => {
emit('update:modelValue', { ...props.modelValue, [key]: value })
}
const onCategoryChange = async (val) => {
emit('update:modelValue', {
...props.modelValue,
material_category: val,
material_subcategory: ''
})
if (!val) {
const data = await fetchSubcategories({})
subcategoryOptions.value = (data.results || data).map((item) => ({
id: item.id,
name: item.name,
value: item.value,
category: item.category
}))
return
}
const category = categoryOptions.value.find((item) => item.value === val)
if (category) {
const data = await fetchSubcategories({ category_id: category.id })
subcategoryOptions.value = (data.results || data).map((item) => ({
id: item.id,
name: item.name,
value: item.value,
category: item.category
}))
}
}
const searchBrandsForForm = async (query) => {
brandFormSearchLoading.value = true
try {
const data = await fetchBrands({ page_size: 50, search: query || '' })
brandFormOptions.value = data.results || data
} finally {
brandFormSearchLoading.value = false
}
}
const ensureBrandInFormOptions = (brandId, brandName) => {
if (!brandId) return
if (!brandFormOptions.value.some((item) => item.id === brandId)) {
brandFormOptions.value = [{ id: brandId, name: brandName || '' }, ...brandFormOptions.value]
}
}
const handleUpload = async (options) => {
uploading.value = true
try {
const result = await uploadImage(options.file)
emit('update:modelValue', {
...props.modelValue,
brochure: result.path,
brochure_url: result.url
})
ElMessage.success('图片上传成功')
} catch {
ElMessage.error('图片上传失败')
} finally {
uploading.value = false
}
}
const displayText = (value) => (value === null || value === undefined || value === '' ? '-' : value)
const displayList = (value) => (value?.length ? value.join('、') : '-')
const formatPercent = (value) => (value === null || value === undefined || value === '' ? '-' : `${value}%`)
const formatStarLevel = (value) => (value ? `${value}` : '-')
onMounted(async () => {
if (props.mode !== 'edit') return
const [choices, categories, subs] = await Promise.all([
fetchMaterialChoices(),
fetchCategories(),
fetchSubcategories({})
])
majorOptions.value = choices.major_category
stageOptions.value = choices.stage
importanceLevelOptions.value = choices.importance_level
replaceOptions.value = choices.replace_type
advantageOptions.value = choices.advantage
sceneOptions.value = choices.application_scene
starOptions.value = choices.star_level
categoryOptions.value = (categories.results || categories).map((item) => ({
id: item.id,
name: item.name,
value: item.value
}))
subcategoryOptions.value = (subs.results || subs).map((item) => ({
id: item.id,
name: item.name,
value: item.value,
category: item.category
}))
factories.value = await fetchFactorySimple()
await searchBrandsForForm('')
ensureBrandInFormOptions(props.modelValue.brand, props.modelValue.brand_name)
if (props.modelValue.material_category) {
const category = categoryOptions.value.find((item) => item.value === props.modelValue.material_category)
if (category) {
const data = await fetchSubcategories({ category_id: category.id })
subcategoryOptions.value = (data.results || data).map((item) => ({
id: item.id,
name: item.name,
value: item.value,
category: item.category
}))
}
}
})
watch(
() => props.modelValue?.brand,
(val) => {
if (val) {
ensureBrandInFormOptions(val, props.modelValue?.brand_name)
}
}
)
const validate = () => formRef.value?.validate()
const clearValidate = () => formRef.value?.clearValidate()
defineExpose({ validate, clearValidate })
</script>
<style scoped>
.upload {
display: inline-block;
}
.preview {
margin-top: 12px;
}
.preview img {
max-width: 300px;
border-radius: 6px;
border: 1px solid #eee;
}
.brochure {
margin-top: 20px;
}
.brochure-title {
margin-bottom: 12px;
font-size: 16px;
font-weight: 600;
}
.brochure img {
max-width: 100%;
border-radius: 8px;
border: 1px solid #eee;
}
</style>