huangxw před 1 měsícem
rodič
revize
a2559e35b9

+ 17 - 0
src/utils/ruoyi.ts

@@ -293,3 +293,20 @@ export const dataURLtoBlob = (base64Buf: string): Blob => {
     }
     return new Blob([u8arr], { type: mime });
 };
+// 传入一个时间2025-11-21 16:56:10,返回今天昨天前天,如果不是返回2022-09-09格式根据日期
+export const formatDateRelative = (date: Date): string => {
+    const now = new Date();
+    const inputDate = new Date(date);
+    const diffTime = now.getTime() - inputDate.getTime();
+    const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
+
+    if (diffDays === 0) {
+        return '今天';
+    } else if (diffDays === 1) {
+        return '昨天';
+    } else if (diffDays === 2) {
+        return '前天';
+    } else {
+        return parseTime(inputDate, '{y}-{m}-{d}') as string;
+    }
+};

+ 21 - 0
src/views/models/FileLook.vue

@@ -30,6 +30,7 @@ import { changeByte } from '@/utils/ruoyi';
 
 const props = defineProps({
     modelValue: [String, Object, Array],
+    value: [String, Object, Array],
     span: propTypes.number.def(18),
     isObject: propTypes.bool.def(true)
 });
@@ -53,6 +54,26 @@ watch(
     },
     { deep: true, immediate: true }
 );
+watch(
+    () => props.value,
+    async (val) => {
+        console.log(val);
+        
+        if (val) {
+            let list = [];
+            if (Array.isArray(val)) {
+                list = val;
+            } else if (props.isObject) {
+                list = [val];
+            }
+            fileList.value = [...list];
+        } else {
+            fileList.value = [];
+            return [];
+        }
+    },
+    {  immediate: true }
+);
 const handleDownload = () => {};
 </script>
 

+ 137 - 0
src/views/raw-materials/index/index.vue

@@ -0,0 +1,137 @@
+<template>
+    <div class="p-3">
+        <div class="bg-fff flex1 ov-hd d-flex flex-cln">
+            <div class="pd-16 border-bottom">
+                <div class="f-s-20 c-333 f-w-7 mb-10">原料求购</div>
+                <div class="d-flex j-ed">
+                    <div class="d-flex pl-20">
+                        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto">
+                            <el-form-item label="原料类型" prop="type">
+                                <el-select style="width: 180px;" @change="resetQuery" v-model="queryParams.type" placeholder="搜原料类型" clearable>
+                                    <el-option v-for="item in dm_medicine_find_type" :key="item.value" :label="item.label" :value="item.value" />
+                                </el-select>
+                            </el-form-item>
+                            <el-form-item label="原料名称" prop="name">
+                                <el-input style="width: 180px;" v-model="queryParams.name" placeholder="搜原料名称" clearable />
+                            </el-form-item>
+                            <el-form-item label="联系电话" prop="tel">
+                                <el-input style="width: 180px;" v-model="queryParams.tel" placeholder="搜联系电话" clearable />
+                            </el-form-item>
+                            <el-form-item>
+                                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                                <el-button @click="exportList">导出</el-button>
+                            </el-form-item>
+                        </el-form>
+                    </div>
+                </div>
+            </div>
+            <div class="flex1 ov-hd pd-16 d-flex flex-cln">
+                <div class="flex1 ov-hd">
+                    <vxe-table ref="tableRef" :loading="loading" border :data="list" min-height="0" max-height="auto" :column-config="{ resizable: true }" :row-config="{ keyField: 'id',  isHover: true }">
+                        <!-- 序号 -->
+                        <vxe-column type="seq" width="60" title="序号" align="center" />
+                        <vxe-column title="原料类型" width="100">
+                            <template #default="{ row }">
+                                {{ selectDictLabel(dm_medicine_find_type, row.type) }}
+                            </template>
+                        </vxe-column>
+                        <vxe-column title="原料名称" field="name" min-width="100" :formatter="colNoData" />
+
+                        <vxe-column title="需求量" width="100">
+                            <template #default="{ row }">{{ row?.amount }}{{ row?.unit }}</template>
+                        </vxe-column>
+                        <vxe-column title="具体厚度/直径" field="spec" width="120" :formatter="colNoData" />
+                        <vxe-column title="具体需求" show-overflow field="needsDetail" min-width="100" :formatter="colNoData" />
+                        <vxe-column title="所在城市" field="adcdName" min-width="100" :formatter="colNoData" />
+                        <vxe-column title="联系电话" field="tel" width="120" :formatter="colNoData" />
+                        <vxe-column title="发布时间" field="createTime" min-width="100" :formatter="colNoData" />
+                        <vxe-column title="供应信息" width="140">
+                            <template #default="{ row }">
+                                <div class="d-flex j-c a-c">
+                                    <el-button @click="clickLookSupply(row)" type="primary" v-if="row?.resCount" text>
+                                        <span>{{ row?.resCount }}条</span>
+                                    </el-button>
+                                    <span v-else>0条</span>
+                                </div>
+                            </template>
+                        </vxe-column>
+                        <vxe-column title="操作" align="center" fixed="right" width="130">
+                            <template #default="{ row }">
+                                <el-button @click="clickAddSupply(row)" type="primary" text>填写供应信息</el-button>
+                            </template>
+                        </vxe-column>
+                    </vxe-table>
+                </div>
+            </div>
+            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+            <div class="pd-5"></div>
+        </div>
+    </div>
+    <CreateSupplyInfo @success="handleQuery" v-if="showSupplyRow" v-model:show="showSupplyRow" :info="rowInfo" :dict="{ dm_medicine_find_type, yes_no, solon_sample_free, solon_report_type }"></CreateSupplyInfo>
+    <ViewsSupplyInfo v-if="showSupplyInfo" v-model:show="showSupplyInfo" :info="rowInfo" :dict="{ dm_medicine_find_type, yes_no, solon_sample_free, solon_report_type }"></ViewsSupplyInfo>
+</template>
+<script setup lang="ts" name="raw-materials-index">
+import { colNoData } from '@/utils/noData';
+import NP from 'number-precision';
+import { priceReportExport } from '@/api/price/report';
+import { httpRequests } from '@/utils/httpRequests';
+import { CreateSupplyInfo, ViewsSupplyInfo } from '../models';
+const router = useRouter();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { dm_medicine_find_type, yes_no, solon_sample_free, solon_report_type } = toRefs<any>(proxy?.useDict('dm_medicine_find_type', 'yes_no', 'solon_sample_free', 'solon_report_type'));
+const queryParams = ref<any>({
+    pageNum: 1,
+    pageSize: 10,
+    auditStatus: '',
+    startDate: '',
+    endDate: '',
+    dateRange: []
+});
+const showSupplyRow = ref(false); // 显示填写供应信息弹窗
+const showSupplyInfo = ref(false); // 显示填写供应信息弹窗
+const loading = ref(false);
+const total = ref(0);
+const list = ref<any>([]);
+const getList = async () => {
+    loading.value = true;
+    const res: any = await httpRequests.get('/solon/medicine-identify/findPageListNoLogin', queryParams.value);
+    if (!res || res.code !== 200) return;
+    list.value = res.rows;
+    total.value = res.total;
+    loading.value = false;
+};
+const handleQuery = () => {
+    queryParams.value.pageNum = 1;
+    getList();
+};
+const queryFormRef = ref<any>();
+const resetQuery = () => {
+    queryFormRef.value?.resetFields();
+    queryParams.value.startDate = '';
+    queryParams.value.endDate = '';
+    handleQuery();
+};
+const rowInfo = ref<any>(null);
+const clickAddSupply = (row: any) => {
+    rowInfo.value = { ...row };
+    showSupplyRow.value = true;
+};
+const clickLookSupply = (row: any) => {
+    rowInfo.value = { ...row };
+    showSupplyInfo.value = true;
+};
+const tableRef = ref<any>();
+const exportList = async () => {
+    // 获取表格选中信息
+    const selected = tableRef.value?.getCheckboxReserveRecords(true).concat(tableRef.value?.getCheckboxRecords());
+    console.log(selected);
+    const ids = selected.map((item: any) => item.id);
+    const res = await priceReportExport({ ids });
+    if (!res || res.code !== 200) return;
+};
+onMounted(() => {
+    handleQuery();
+});
+</script>
+<style lang="scss"></style>

+ 212 - 0
src/views/raw-materials/models/create-supply-info.vue

@@ -0,0 +1,212 @@
+<template>
+    <vxe-modal v-model="dialogVisible" :title="title" show-zoom resize show-footer destroy-on-close transfer @hide="close" :width="width" :z-index="1002">
+        <template #default>
+            <div class="over-auto" style="height: 80vh;">
+                <div class="pd-16 bg-#f7f7f7 mb-16">
+                    <div class="mb-10 d-flex">
+                        <div class="flex1 ov-hd">
+                            <el-tag class="mr-8" type="success" effect="dark">求购</el-tag>
+                            <span class="f-s-16 f-w-5 c-333">{{ info?.name }}</span>
+                            <span class="f-s-12 c-333">({{ selectDictLabel(dm_medicine_find_type, info?.type) }})</span>
+                        </div>
+                        <div class="c-999 f-s-14">{{ formatDateRelative(info?.createTime) }}发布</div>
+                    </div>
+                    <div>具体需求:{{ info?.needsDetail }}</div>
+                </div>
+                <div class="info-title f-s-16 c-333 f-w-5 mb-10">供应信息</div>
+                <el-form class="ov-hd" ref="formRef" :model="form" :rules="rules" label-width="auto" label-position="top">
+                    <div class="f-s-14 c-333 f-w-5 mb-10">报价信息</div>
+                    <el-form-item label="价格" prop="price">
+                        <div class="flex1 ov-hd">
+                            <el-input class="mr-20" style="width: 200px;" v-model="form.price" placeholder="请输入含税单价" clearable>
+                                <template #prefix>¥</template>
+                                <template #suffix>/kg</template>
+                            </el-input>
+                            <el-checkbox v-model="form.priceType" true-value="1" false-value="0">具体再议</el-checkbox>
+                        </div>
+                    </el-form-item>
+                    <el-form-item label="价格补充说明" prop="remark">
+                        <el-input type="textarea" :rows="3" maxlength="300" show-word-limit v-model="form.remark" placeholder="可对价格有效期或其他需要特别说明的价格等事项进行补充~" clearable />
+                    </el-form-item>
+                    <div class="h-1 bg-#f7f7f7 mg2-10-0"></div>
+                    <el-form-item label="原料图" prop="images">
+                        <imageUpload v-model="form.images" :limit="50" isString :isShowTip="false"></imageUpload>
+                    </el-form-item>
+                    <el-form-item label="原料优势" prop="tags">
+                        <el-select v-model="form.tags" multiple filterable allow-create default-first-option :reserve-keyword="false" placeholder="请输入或选择您的原料优势" style="width: 320px">
+                            <el-option v-for="item in tabs" :key="item.value" :label="item.label" :value="item.value" />
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item label="检验报告" prop="reportType">
+                        <el-radio-group v-model="form.reportType">
+                            <el-radio v-for="item in solon_report_type" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                    <el-form-item v-if="+form.reportType" prop="report">
+                        <FileUpload v-model="form.report" format="array" :fileType="['pdf', 'png', 'jpg', 'jpeg']" :limit="10" :fileSize="100"></FileUpload>
+                    </el-form-item>
+                    <el-form-item v-else>
+                        <div>
+                            <div>
+                                <el-checkbox true-value="1" false-value="0" v-model="form.providePha" label="后期可根据需要提供《中国药典》检测报告" />
+                            </div>
+                            <div>
+                                <el-checkbox true-value="1" false-value="0" v-model="form.provideMetal" label="后期可根据需要提供重金属/农残专项检测报告" />
+                            </div>
+                        </div>
+                    </el-form-item>
+                    <el-form-item label="是否提供样品" prop="provideSample">
+                        <el-radio-group v-model="form.provideSample">
+                            <el-radio v-for="item in yes_no" :key="item.value" :label="item.value">
+                                {{ item.label }}
+                            </el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                    <el-form-item v-if="+form.provideSample" label="样品费用" prop="sampleFree">
+                        <el-radio-group v-model="form.sampleFree">
+                            <el-radio v-for="item in solon_sample_free" :key="item.value" :label="item.value">
+                                {{ item.label }}
+                            </el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                    <el-form-item v-if="+form.provideSample && +form.sampleFree" label="样品含税单价" prop="samplePrice">
+                        <el-input class="mr-20" style="width: 200px;" v-model="form.samplePrice" placeholder="请输入样品含税单价" clearable>
+                            <template #prefix>¥</template>
+                            <template #suffix>/kg</template>
+                        </el-input>
+                    </el-form-item>
+                    <div class="h-1 bg-#f7f7f7 mg2-10-0"></div>
+                    <div class="f-s-14 c-333 f-w-5 mb-10">企业信息</div>
+                    <el-row :gutter="16">
+                        <el-col :span="12">
+                            <el-form-item label="企业名称" prop="cpyName">
+                                <el-input v-model="form.cpyName" placeholder="请输入企业名称" clearable />
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="12">
+                            <el-form-item label="仓库所在地" prop="warehouseAddress">
+                                <AreaCascader v-model="form.warehouseAddress" :zlevel="2"></AreaCascader>
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="12">
+                            <el-form-item label="联系人姓名" prop="contactName">
+                                <el-input v-model="form.contactName" placeholder="请输入联系人姓名" clearable />
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="12">
+                            <el-form-item label="联系电话" prop="tel">
+                                <el-input v-model="form.tel" placeholder="请输入联系电话" clearable />
+                            </el-form-item>
+                        </el-col>
+                    </el-row>
+                </el-form>
+            </div>
+        </template>
+        <template #footer>
+            <el-button @click="close">取消</el-button>
+            <el-button type="primary" @click="submitForm">确认修改</el-button>
+        </template>
+    </vxe-modal>
+</template>
+
+<script setup name="create-supply-info" lang="ts">
+import { httpRequests } from '@/utils/httpRequests';
+import { propTypes } from '@/utils/propTypes';
+import { formatDateRelative } from '@/utils/ruoyi';
+import { AreaCascader } from '@/views/components';
+import { FormInstance } from 'element-plus';
+const emit = defineEmits(['update:show', 'close', 'success']);
+const props = defineProps({
+    show: propTypes.bool.def(false),
+    title: propTypes.string.def('填写供应信息'),
+    width: propTypes.number.def(760),
+    info: propTypes.any.def(null),
+    dict: propTypes.any.def(null)
+});
+const { dm_medicine_find_type, yes_no, solon_sample_free, solon_report_type } = toRefs(props.dict);
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const dialogVisible = ref(props.show);
+const form = ref<any>({
+    price: '',
+    priceType: 0, // 0-有具体价格,1-具体再议
+    remark: '',
+    images: '',
+    tags: [],
+    reportType: '',
+    report: [],
+    provideSample: 0, // 0-不提供样品,1-提供样品
+    sampleFree: '', // 样品费用
+    samplePrice: '',
+    cpyName: '',
+    warehouseAddress: '',
+    contactName: '',
+    tel: ''
+});
+const rules = reactive<any>({
+    price: [
+        {
+            validator: (rule: any, value: any, callback: any) => {
+                // 具体再议时,跳过价格必填与格式校验
+                if (+form.value.priceType) return callback();
+                const v = String(value ?? '').trim();
+                if (!v) return callback(new Error('请输入价格'));
+                // 最多两位小数的正数
+                if (!/^\d+(?:\.\d{1,2})?$/.test(v)) return callback(new Error('最多保留两位小数'));
+                const num = Number(v);
+                if (!(num > 0)) return callback(new Error('价格必须大于0'));
+                return callback();
+            },
+            trigger: ['change', 'blur']
+        }
+    ],
+    images: [{ required: true, message: '请上传原料图片', trigger: 'change' }],
+    cpyName: [{ required: true, message: '请输入企业名称', trigger: 'change' }],
+    warehouseAddress: [{ required: true, message: '请输入仓库所在地', trigger: 'change' }],
+    contactName: [{ required: true, message: '请输入联系人姓名', trigger: 'change' }],
+    tel: [
+        { required: true, message: '请输入联系电话', trigger: 'change' },
+        { pattern: /(^1\d{10}$)|(^(\(\d{3,4}\)|\d{3,4}-|\s)?\d{7,14}$)/, message: '请输入正确的手机号格式', trigger: 'blur' }
+    ]
+});
+
+const formRef = ref<FormInstance>();
+const close = () => {
+    formRef.value?.resetFields();
+    emit('update:show', false);
+    emit('close', false);
+};
+const tabs = ref<any>([
+    { label: '符合GAP要求', value: '符合GAP要求' },
+    { label: '绿色食品认证', value: '绿色食品认证' },
+    { label: '三无一全认定', value: '三无一全认定' },
+    { label: '地理标志产品', value: '地理标志产品' },
+    { label: '有机认证', value: '有机认证' }
+]);
+const submitForm = async () => {
+    try {
+        await formRef.value?.validate();
+        proxy?.$modal.loading('修改中...');
+        console.log(form.value);
+        const res = await httpRequests.post('/solon/medicine-response/addForPc', { ...form.value, findId: props.info?.id, tags: form.value?.tags?.toString() }).finally(() => {
+            proxy?.$modal.closeLoading();
+        });
+        if (res?.code === 200) {
+            proxy?.$modal.msgSuccess('修改成功');
+            emit('success', true);
+            close();
+        }
+    } catch (error) {
+        console.error(error);
+    }
+};
+
+watch(
+    () => props.show,
+    (val) => {
+        dialogVisible.value = val;
+    }
+);
+</script>
+<style lang="scss" scoped>
+// 右上角定位三角形文字,背景主题色,颜色白色
+</style>

+ 3 - 0
src/views/raw-materials/models/index.ts

@@ -0,0 +1,3 @@
+export { default as CreateSupplyInfo } from './create-supply-info.vue'; // 填写供应信息
+export { default as ViewsSupplyInfo } from './views-supply-info.vue'; // 填写供应信息
+export { default as ViewsSupplyItem } from './views-supply-item.vue'; // 填写供应详情

+ 75 - 0
src/views/raw-materials/models/views-supply-info.vue

@@ -0,0 +1,75 @@
+<template>
+    <vxe-modal v-model="dialogVisible" :title="`供应信息(共${list.length}条)`" show-zoom resize show-footer destroy-on-close
+        transfer @hide="close" :width="width" :z-index="1002">
+        <template #default>
+            <div class="over-auto" style="height: 80vh;">
+                <div class="pd-16 bg-#f7f7f7 mb-16">
+                    <div class="mb-10 d-flex">
+                        <div class="flex1 ov-hd">
+                            <el-tag class="mr-8" type="success" effect="dark">求购</el-tag>
+                            <span class="f-s-16 f-w-5 c-333">{{ info?.name }}</span>
+                            <span class="f-s-12 c-333">({{ selectDictLabel(dm_medicine_find_type, info?.type) }})</span>
+                        </div>
+                        <div class="c-999 f-s-14">{{ formatDateRelative(info?.createTime) }}发布</div>
+                    </div>
+                    <div>具体需求:{{ info?.needsDetail }}</div>
+                </div>
+                <div class="info-title f-s-16 c-333 f-w-5 mb-10">供应信息</div>
+                <div v-if="!list.length" class="f-s-14 c-999 t-c p-t-100">暂无供应信息~</div>
+                <div v-else>
+                    <el-tabs type="card" class="demo-tabs">
+                        <el-tab-pane v-for="(item, index) in list" :key="item.id" :label="`供应信息${index + 1}`"
+                            :name="item.name">
+                            <ViewsSupplyItem :info="item" :dict="dict"></ViewsSupplyItem>
+                        </el-tab-pane>
+                    </el-tabs>
+                </div>
+            </div>
+        </template>
+    </vxe-modal>
+</template>
+
+<script setup name="views-supply-info" lang="ts">
+import { httpRequests } from '@/utils/httpRequests';
+import { propTypes } from '@/utils/propTypes';
+import { formatDateRelative } from '@/utils/ruoyi';
+import { AreaCascader } from '@/views/components';
+import { FormInstance } from 'element-plus';
+import { ViewsSupplyItem } from '.';
+const emit = defineEmits(['update:show', 'close', 'success']);
+const props = defineProps({
+    show: propTypes.bool.def(false),
+    width: propTypes.number.def(760),
+    info: propTypes.any.def(null),
+    dict: propTypes.any.def(null)
+});
+const { dm_medicine_find_type, yes_no, solon_sample_free, solon_report_type } = toRefs(props.dict);
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const dialogVisible = ref(props.show);
+const list = ref<any[]>([]);
+const formRef = ref<FormInstance>();
+const close = () => {
+    formRef.value?.resetFields();
+    emit('update:show', false);
+    emit('close', false);
+};
+// 获取供应详情
+const getSupplyInfo = async (id: string) => {
+    const res = await httpRequests.get(`/solon/medicine-identify/getFindInfo/${id}`,);
+    console.log(res);
+    if (res?.code === 200) {
+        list.value = res?.data?.resList || [];
+    }
+};
+watch(
+    () => props.show,
+    (val) => {
+        dialogVisible.value = val;
+    }
+);
+onMounted(() => {
+    getSupplyInfo(props.info?.id);
+});
+</script>
+<style lang="scss" scoped>
+// 右上角定位三角形文字,背景主题色,颜色白色</style>

+ 235 - 0
src/views/raw-materials/models/views-supply-item.vue

@@ -0,0 +1,235 @@
+<template>
+    <div class="detail-wrap">
+        <div class="f-s-14 c-333 f-w-5 mb-10">原料信息</div>
+        <el-row :gutter="16" class="mb-10">
+            <el-col :span="24" class="field" v-if="info?.type !== undefined && info?.type !== null">
+                <span class="label">找药类型</span>
+                <span class="value">{{ selectDictLabel(dm_medicine_find_type, info?.type) || '-' }}</span>
+            </el-col>
+
+            <el-col :span="24" class="field">
+                <span class="label">价格</span>
+                <span class="value">{{ displayPrice }}</span>
+            </el-col>
+
+            <el-col :span="24" class="field" v-if="info?.priceType === '1'">
+                <span class="label">价格说明</span>
+                <span class="value">具体再议</span>
+            </el-col>
+
+            <el-col :span="24" class="field" v-if="info?.remark">
+                <span class="label">价格补充说明</span>
+                <span class="value">{{ info.remark }}</span>
+            </el-col>
+
+            <el-col :span="24" class="field" v-if="imgList.length">
+                <span class="label">原料图</span>
+                <div class="value img-list">
+                    <el-image v-for="(img, idx) in imgList" :key="idx" :src="img" :preview-src-list="imgList" fit="cover" class="thumb" />
+                </div>
+            </el-col>
+
+            <el-col :span="24" class="field" v-if="tagList.length">
+                <span class="label">原料优势</span>
+                <div class="value">
+                    <el-tag v-for="(t, i) in tagList" :key="i" class="mr-8 mb-8">{{ t }}</el-tag>
+                </div>
+            </el-col>
+
+            <el-col :span="24" class="field">
+                <span class="label">检验报告</span>
+                <div class="value">
+                    <span>{{ selectDictLabel(solon_report_type, info?.reportType) || (+((info?.reportType || 0)) ? '有' : '无') }}</span>
+                </div>
+            </el-col>
+            <el-col v-if="info?.report?.length" :span="24" class="field">
+                <div>
+                    <FileLook :value="info.report"></FileLook>
+                </div>
+            </el-col>
+            <el-col v-if="+info?.providePha" :span="24" class="field">
+                <div>后期可根据需要提供《中国药典》检测报告</div>
+            </el-col>
+            <el-col v-if="+info?.provideMetal" :span="24" class="field">
+                <div>后期可根据需要提供重金属/农残专项检测报告</div>
+            </el-col>
+            <el-col :span="24" class="field">
+                <span class="label">是否提供样品</span>
+                <span class="value">{{ selectDictLabel(yes_no, info?.provideSample) || (+info?.provideSample ? '是' : '否') }}</span>
+            </el-col>
+
+            <el-col :span="24" class="field" v-if="+(info?.provideSample || 0)">
+                <span class="label">样品费用</span>
+                <span class="value">{{ selectDictLabel(solon_sample_free, info?.sampleFree) || (+info?.sampleFree ? '免费' : '收费') }}</span>
+            </el-col>
+
+            <el-col :span="24" class="field" v-if="+(info?.provideSample || 0) && +(info?.sampleFree || 0)">
+                <span class="label">样品含税单价</span>
+                <span class="value">{{ displaySamplePrice }}</span>
+            </el-col>
+        </el-row>
+
+        <div class="h-1 bg-#f7f7f7 mg2-10-0"></div>
+
+        <div class="f-s-14 c-333 f-w-5 mb-10">企业信息</div>
+        <el-row :gutter="16">
+            <el-col :span="12">
+                <div class="field">
+                    <span class="label">企业名称</span>
+                    <span class="value">{{ info?.cpyName || '-' }}</span>
+                </div>
+            </el-col>
+            <el-col :span="12">
+                <div class="field">
+                    <span class="label">仓库所在地</span>
+                    <span class="value">{{ displayArea }}</span>
+                </div>
+            </el-col>
+            <el-col :span="12">
+                <div class="field">
+                    <span class="label">联系人姓名</span>
+                    <span class="value">{{ info?.contactName || '-' }}</span>
+                </div>
+            </el-col>
+            <el-col :span="12">
+                <div class="field">
+                    <span class="label">联系电话</span>
+                    <span class="value">{{ info?.tel || '-' }}</span>
+                </div>
+            </el-col>
+        </el-row>
+    </div>
+</template>
+
+<script setup name="views-supply-item" lang="ts">
+import { computed } from 'vue';
+import { propTypes } from '@/utils/propTypes';
+import { FileLook } from '@/views/models';
+
+const props = defineProps({
+    info: propTypes.any.def(null),
+    dict: propTypes.any.def(null)
+});
+
+// 字典列表(避免 props.dict 为空)
+const dm_medicine_find_type = computed(() => props.dict?.dm_medicine_find_type || []);
+const yes_no = computed(() => props.dict?.yes_no || []);
+const solon_sample_free = computed(() => props.dict?.solon_sample_free || []);
+const solon_report_type = computed(() => props.dict?.solon_report_type || []);
+
+// 统一字典取 label
+function selectDictLabel(list: any[] = [], value: any) {
+    const hit = (list || []).find((i: any) => String(i.value) === String(value));
+    return hit?.label ?? '';
+}
+
+// 价格显示
+const displayPrice = computed(() => {
+    const info = props.info || {};
+    if (info.priceType === '1') return '具体再议';
+    if (info.price === 0 || info.price) return `¥${Number(info.price).toLocaleString()}/kg`;
+    return '-';
+});
+
+// 样品价格显示
+const displaySamplePrice = computed(() => {
+    const price = props.info?.samplePrice;
+    if (price === 0 || price) return `¥${Number(price).toLocaleString()}/kg`;
+    return '-';
+});
+
+// 原料图列表(兼容字符串、数组)
+const imgList = computed<string[]>(() => {
+    const v = props.info?.images;
+    if (!v) return [];
+    if (Array.isArray(v)) return v.filter(Boolean);
+    if (typeof v === 'string') {
+        // 逗号或分号分隔
+        return v.split(/[,;]\s*/).filter(Boolean);
+    }
+    return [];
+});
+
+// 原料优势标签
+const tagList = computed<string[]>(() => {
+    const v = props.info?.tags;
+    if (!v) return [];
+    if (Array.isArray(v)) return v.filter(Boolean);
+    if (typeof v === 'string') return v.split(/[,;]\s*/).filter(Boolean);
+    return [];
+});
+
+// 检验报告文件
+const reportFiles = computed<any[]>(() => {
+    const v = props.info?.report;
+    if (!v) return [];
+    if (Array.isArray(v)) return v;
+    if (typeof v === 'string') return v ? [v] : [];
+    return [];
+});
+
+function getFileUrl(item: any) {
+    if (!item) return '#';
+    if (typeof item === 'string') return item;
+    return item.url || item.path || '#';
+}
+function getFileName(item: any) {
+    if (!item) return '';
+    if (typeof item === 'string') {
+        try {
+            const url = new URL(item, window.location.origin);
+            return decodeURIComponent(url.pathname.split('/').pop() || '文件');
+        } catch {
+            const segs = item.split('/');
+            return decodeURIComponent(segs[segs.length - 1] || '文件');
+        }
+    }
+    return item.name || item.filename || '文件';
+}
+
+// 仓库所在地显示(兼容数组/字符串/对象)
+const displayArea = computed(() => {
+    const v = props.info?.warehouseAddress;
+    if (!v) return '-';
+    if (Array.isArray(v)) return v.filter(Boolean).join(' / ');
+    if (typeof v === 'object') {
+        const { province, city, district, name } = v as any;
+        const parts = [province, city, district, name].filter(Boolean);
+        return parts.length ? parts.join(' / ') : '-';
+    }
+    return String(v);
+});
+</script>
+
+<style lang="scss" scoped>
+.detail-wrap {
+    .field {
+        display: flex;
+        align-items: flex-start;
+        padding: 6px 0;
+        .label {
+            color: #666;
+            min-width: 120px;
+            flex: 0 0 auto;
+        }
+        .value {
+            color: #333;
+            flex: 1;
+            word-break: break-all;
+        }
+    }
+    .img-list {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 8px;
+        .thumb {
+            width: 80px;
+            height: 80px;
+            border-radius: 4px;
+            overflow: hidden;
+            border: 1px solid #f0f0f0;
+            background: #fafafa;
+        }
+    }
+}
+</style>