9
0

3 کامیت‌ها 11ef003dae ... c698a7876d

نویسنده SHA1 پیام تاریخ
  Lijingyang c698a7876d Merge remote-tracking branch 'origin/master' 1 ماه پیش
  Lijingyang 4bc0376844 feat(supplier): 移除供应商信息页面中的地址对话框和品牌对话框组件 1 ماه پیش
  Lijingyang 352c50bee2 供应商信息和界面展示 1 ماه پیش

+ 1 - 1
src/api/supplier/area/index.ts

@@ -10,7 +10,7 @@ import { AreaVO, AreaForm, AreaQuery } from '@/api/supplier/area/types';
 
 export const listArea = (query?: AreaQuery): AxiosPromise<AreaVO[]> => {
   return request({
-    url: '/customer/area/srm/list',
+    url: '/customer/area/list',
     method: 'get',
     params: query
   });

+ 1 - 1
src/api/supplier/businessInfo/index.ts

@@ -1,6 +1,6 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
-import { BusinessInfoVO, BusinessInfoForm, BusinessInfoQuery } from '@/api/suuplier/businessInfo/types';
+import { BusinessInfoVO, BusinessInfoForm, BusinessInfoQuery } from '@/api/supplier/businessInfo/types';
 
 /**
  * 查询供应商工商注册信息列表

+ 8 - 0
src/api/supplier/info/index.ts

@@ -66,6 +66,14 @@ export const updateInfo = (data: InfoForm) => {
   });
 };
 
+export const scmEditInfo = (data: InfoForm) => {
+  return request({
+    url: '/customer/info/edit',
+    method: 'put',
+    data: data
+  });
+};
+
 /**
  * 删除供应商信息
  * @param id

+ 51 - 16
src/components/ImageUpload/index.vue

@@ -3,6 +3,7 @@
     <el-upload
       ref="imageUploadRef"
       multiple
+      :disabled="disabled"
       :action="uploadImgUrl"
       list-type="picture-card"
       :on-success="handleUploadSuccess"
@@ -52,6 +53,10 @@ const props = defineProps({
     type: [String, Object, Array],
     default: () => []
   },
+  disabled: {
+    type: Boolean,
+    default: false
+  },
   // 图片数量限制
   limit: propTypes.number.def(5),
   // 大小限制(MB)
@@ -93,36 +98,62 @@ const fileAccept = computed(() => props.fileType.map((type) => `.${type}`).join(
 
 watch(
   () => props.modelValue,
-  async (val: string) => {
+  async (val: any) => {
     if (val) {
       // 首先将值转为数组
-      let list: OssVO[] = [];
+      let list: any[] = [];
       if (Array.isArray(val)) {
-        list = val as OssVO[];
-      } else {
-        const res = await listByIds(val);
-        list = res.data;
+        list = val;
+      } else if (typeof val === 'string') {
+        // 判断是否为路径模式 (包含 / 或 http)
+        if (val.includes('/') || val.includes('http')) {
+          list = val.split(',').map((url) => ({ url: url.trim(), name: url.trim() }));
+        } else {
+          try {
+            const res = await listByIds(val);
+            list = res.data || [];
+          } catch (e) {
+            console.error('Failed to list image by IDs:', val);
+            list = [];
+          }
+        }
       }
+
       // 然后将数组转为对象数组
       fileList.value = list.map((item) => {
-        // 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
         let itemData;
         if (typeof item === 'string') {
-          itemData = { name: item, url: item };
+          itemData = { name: item, url: formatUrl(item) };
         } else {
-          // 此处name使用ossId 防止删除出现重名
-          itemData = { name: item.ossId, url: item.url, ossId: item.ossId };
+          const name = item.ossId || item.url || 'image';
+          itemData = { name: name, url: formatUrl(item.url), ossId: item.ossId };
         }
         return itemData;
       });
     } else {
       fileList.value = [];
-      return [];
     }
   },
   { deep: true, immediate: true }
 );
 
+// 辅助函数:确保 URL 是正确的格式
+function formatUrl(url: string) {
+  if (!url) return '';
+  // 如果是 full URL (http/https 或 blob) 直接返回
+  if (url.startsWith('http') || url.startsWith('blob:')) {
+    return url;
+  }
+  // 如果是相对路径,补全 baseUrl (如果是 /profile 等路径,ruoyi 通常会自动处理,但此处手动防误)
+  // 注意:如果后端返回的是带 / 的路径且没有域名,这里需要根据实际情况补全
+  if (url.startsWith('/')) {
+    // 如果 baseUrl 结尾没有 /,且 url 开头有 /,需要处理
+    const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
+    return base + url;
+  }
+  return url;
+}
+
 /** 上传前loading加载 */
 const handleBeforeUpload = (file: any) => {
   let isImg = false;
@@ -187,10 +218,14 @@ const handleUploadSuccess = (res: any, file: UploadFile) => {
 
 // 删除图片
 const handleDelete = (file: UploadFile): boolean => {
-  const findex = fileList.value.map((f) => f.name).indexOf(file.name);
+  const findex = fileList.value.findIndex((f) => f.name === file.name || f.url === file.url);
   if (findex > -1 && uploadList.value.length === number.value) {
-    const ossId = fileList.value[findex].ossId;
-    delOss(ossId);
+    const item = fileList.value[findex];
+    if (item.ossId) {
+      delOss(item.ossId).catch(() => {
+        console.warn('Physical delete failed, but removing from UI.');
+      });
+    }
     fileList.value.splice(findex, 1);
     emit('update:modelValue', listToString(fileList.value));
     return false;
@@ -226,8 +261,8 @@ const listToString = (list: any[], separator?: string) => {
   let strs = '';
   separator = separator || ',';
   for (const i in list) {
-    if (undefined !== list[i].ossId && list[i].url.indexOf('blob:') !== 0) {
-      strs += list[i].ossId + separator;
+    if (undefined !== list[i].url && list[i].url.indexOf('blob:') !== 0) {
+      strs += list[i].url + separator;
     }
   }
   return strs != '' ? strs.substring(0, strs.length - 1) : '';

+ 13 - 0
src/router/index.ts

@@ -101,6 +101,19 @@ export const constantRoutes: RouteRecordRaw[] = [
         meta: { title: '新增产品线授权', activeMenu: '/supplier/author' }
       }
     ]
+  },
+  {
+    path: '/supplier/author/detail',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: '',
+        component: () => import('@/views/supplier/author/detail.vue'),
+        name: 'AuthorDetail',
+        meta: { title: '查看授权详情', activeMenu: '/supplier/author' }
+      }
+    ]
   }
 ];
 

+ 407 - 0
src/views/supplier/address/index.vue

@@ -0,0 +1,407 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item label="供应商编号" prop="supplierNo" label-width="100px">
+              <el-input v-model="queryParams.supplierNo" placeholder="请输入供应商编号" clearable @keyup.enter="handleQuery" />
+            </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-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['customer:address:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['customer:address:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['customer:address:remove']">删除</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['customer:address:export']">导出</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="addressList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="主键ID" align="center" prop="id" v-if="false" />
+        <el-table-column label="供应商编号" align="center" prop="supplierNo" />
+        <el-table-column label="企业名称" align="center" prop="enterpriseName" width="250"/>
+        <el-table-column label="地址编码" align="center" prop="addressNo" />
+        <el-table-column label="姓名" align="center" prop="shipperName" />
+        <el-table-column label="手机号" align="center" prop="shipperPhone" />
+        <el-table-column label="邮政编码" align="center" prop="shippingPostCode" />
+        <el-table-column label="省" align="center" prop="shippingProvincial" />
+        <el-table-column label="市" align="center" prop="shippingCity" />
+        <el-table-column label="区" align="center" prop="shippingCounty" />
+        <el-table-column label="详细地址" align="center" prop="shippingAddress" />
+        <el-table-column label="默认地址" align="center" prop="isSelf">
+          <template #default="scope">
+            <span>{{ scope.row.isSelf === 1 ? '是' : '否' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" @click="handleUpdate(scope.row)">修改</el-button>
+            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+    <!-- 添加或修改供应商地址对话框 -->
+    <el-dialog
+      v-model="dialog.visible"
+      :title="dialog.title"
+      width="650px"
+      :close-on-click-modal="false"
+    >
+      <el-form
+        ref="addressFormRef"
+        :model="form"
+        :rules="rules"
+        label-width="120px"
+        :disabled="false"
+      >
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="企业名称:" prop="supplierId" required>
+              <el-select 
+                v-model="form.supplierId" 
+                placeholder="请选择企业名称" 
+                clearable 
+                filterable
+                @change="handleSupplierChange"
+              >
+                <el-option
+                  v-for="item in supplierOptions"
+                  :key="item.id"
+                  :label="item.enterpriseName"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="供应商编号:" prop="supplierNo">
+              <el-input v-model="form.supplierNo" disabled placeholder="自动带出" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="收货人:" prop="shipperName" required>
+              <el-input v-model="form.shipperName" placeholder="请输入收货人" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="手机号码:" prop="shipperPhone" required>
+              <el-input v-model="form.shipperPhone" placeholder="请输入手机号码" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="邮政编码:" prop="shippingPostCode">
+              <el-input v-model="form.shippingPostCode" placeholder="请输入邮政编码" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="地址:" prop="shippingProvincial" required>
+              <el-cascader
+                v-model="selectedAddressRegion"
+                :options="regionOptions"
+                placeholder="请选择"
+                clearable
+                filterable
+                style="width: 100%;"
+                @change="handleAddressRegionChange"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="详细地址:" prop="shippingAddress">
+              <el-input
+                v-model="form.shippingAddress"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入详细地址"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="默认地址:">
+              <el-switch v-model="form.isSelf" :active-value="1" :inactive-value="0" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancel">取消</el-button>
+          <el-button type="primary" @click="submitForm" :loading="buttonLoading">确定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Address" lang="ts">
+import { listAddress, getAddress, delAddress, addAddress, updateAddress } from '@/api/supplier/address';
+import { AddressVO, AddressQuery, AddressForm } from '@/api/supplier/address/types';
+import { listNameInfo } from '@/api/supplier/info';
+import { regionData, codeToText } from 'element-china-area-data';
+import { useRoute } from 'vue-router';
+import { useUserStore } from '@/store/modules/user';
+
+const regionOptions = ref(regionData);
+const selectedAddressRegion = ref<string[]>([]);
+const supplierOptions = ref<any[]>([]);
+
+const route = useRoute();
+const userStore = useUserStore();
+
+const getSupplierNameList = async () => {
+  const res = await listNameInfo();
+  supplierOptions.value = res.data || res.rows || [];
+};
+
+const handleSupplierChange = (val: string | number) => {
+  const selected = supplierOptions.value.find(item => item.id === val);
+  if (selected) {
+    form.value.supplierNo = selected.supplierNo;
+    form.value.enterpriseName = selected.enterpriseName;
+  } else {
+    form.value.supplierNo = undefined;
+    form.value.enterpriseName = undefined;
+  }
+};
+
+const handleAddressRegionChange = (value: any) => {
+  if (value && value.length === 3) {
+    form.value.shippingProvincial = codeToText[value[0]];
+    form.value.shippingCity = codeToText[value[1]];
+    form.value.shippingCounty = codeToText[value[2]];
+  } else {
+    form.value.shippingProvincial = undefined;
+    form.value.shippingCity = undefined;
+    form.value.shippingCounty = undefined;
+  }
+};
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const addressList = ref<AddressVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+const addressFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: AddressForm = {
+  id: undefined,
+  supplierNo: undefined,
+  supplierId: undefined,
+  shippingCompany: undefined,
+  shipperName: undefined,
+  shipperPhone: undefined,
+  shippingPostCode: undefined,
+  shippingProvincial: undefined,
+  shippingCity: undefined,
+  shippingCounty: undefined,
+  shippingAddress: undefined,
+  pushStatus: undefined,
+  isSelf: undefined,
+  type: undefined,
+  addressNo: undefined,
+  status: undefined,
+  remark: undefined,
+}
+const data = reactive<PageData<AddressForm, AddressQuery>>({
+  form: {...initFormData},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    supplierNo: undefined,
+    params: {
+    }
+  },
+  rules: {
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询供应商地址列表 */
+const getList = async () => {
+  loading.value = true;
+  if (!userStore.supplierId) {
+    await userStore.getSupplierInfo();
+  }
+  queryParams.value.supplierId = (userStore.supplierId ?? route.query?.supplierId ?? route.query?.id ?? route.params?.supplierId ?? route.params?.id) as any;
+  if (!queryParams.value.supplierId) {
+    loading.value = false;
+    return;
+  }
+  const res = await listAddress(queryParams.value);
+  addressList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+}
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = {...initFormData};
+  selectedAddressRegion.value = [];
+  addressFormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: AddressVO[]) => {
+  ids.value = selection.map(item => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = "添加供应商地址";
+}
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: AddressVO) => {
+  reset();
+  const _id = row?.id || ids.value[0]
+  const res = await getAddress(_id);
+  Object.assign(form.value, res.data);
+  if (res.data.shippingProvincial && res.data.shippingCity && res.data.shippingCounty) {
+    try {
+      // Find region codes using element-china-area-data
+      let pCode = '';
+      let cCode = '';
+      let dCode = '';
+
+      for (const p of regionOptions.value) {
+        if (p.label === res.data.shippingProvincial) {
+          pCode = p.value;
+          for (const c of p.children || []) {
+            if (c.label === res.data.shippingCity) {
+              cCode = c.value;
+              for (const d of c.children || []) {
+                if (d.label === res.data.shippingCounty) {
+                  dCode = d.value;
+                  break;
+                }
+              }
+              break;
+            }
+          }
+          break;
+        }
+      }
+
+      if (pCode && cCode && dCode) {
+        selectedAddressRegion.value = [pCode, cCode, dCode];
+      }
+    } catch (e) {
+      console.error('Failed to parse region codes', e);
+    }
+  }
+  dialog.visible = true;
+  dialog.title = "修改供应商地址";
+}
+
+/** 提交按钮 */
+const submitForm = () => {
+  addressFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateAddress(form.value).finally(() =>  buttonLoading.value = false);
+      } else {
+        await addAddress(form.value).finally(() =>  buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("操作成功");
+      dialog.visible = false;
+      await getList();
+    }
+  });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: AddressVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除供应商地址编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
+  await delAddress(_ids);
+  proxy?.$modal.msgSuccess("删除成功");
+  await getList();
+}
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download('customer/address/export', {
+    ...queryParams.value
+  }, `address_${new Date().getTime()}.xlsx`)
+}
+
+onMounted(() => {
+  getList();
+  getSupplierNameList();
+});
+</script>

+ 7 - 0
src/views/supplier/author/edit.vue

@@ -399,6 +399,7 @@ const categoryDisplayNames = ref({
 // 表单数据
 const formData = ref({
   brandId: '',
+  brandNo: '',
   brandName: '',
   brandLogo: '', // 品牌LOGO
   brandRegistrant: '', // 商标注册人
@@ -564,6 +565,7 @@ const loadDetailData = async () => {
     console.log('详情数据:', data);
     
     // 回显基本信息
+    formData.value.brandNo = (data as any).brandNo || '';
     formData.value.brandName = data.brandName || '';
     formData.value.brandLogo = data.brandLogo || '';
     formData.value.brandRegistrant = data.brandRegistrant || (data as any).registrationCertificate || '';
@@ -723,6 +725,7 @@ const loadDetailData = async () => {
 
 const handleBrandClear = () => {
   formData.value.brandId = '';
+  formData.value.brandNo = '';
   formData.value.brandName = '';
   formData.value.brandLogo = '';
   formData.value.brandRegistrant = '';
@@ -782,6 +785,7 @@ const handleAuthorizeTypeChange = (id: string | number) => {
 const handleBrandChange = (brandName: string) => {
   if (!brandName) {
     formData.value.brandId = '';
+    formData.value.brandNo = '';
     formData.value.brandName = '';
     formData.value.brandLogo = '';
     formData.value.brandRegistrant = '';
@@ -790,11 +794,13 @@ const handleBrandChange = (brandName: string) => {
   const selectedBrand = brandList.value.find(b => b.brandName === brandName);
   if (selectedBrand) {
     formData.value.brandId = selectedBrand.id;
+    formData.value.brandNo = selectedBrand.brandNo || '';
     formData.value.brandName = selectedBrand.brandName;
     formData.value.brandLogo = selectedBrand.brandLogo || ''; // 保存品牌LOGO
     formData.value.brandRegistrant = selectedBrand.registrationCertificate || selectedBrand.brandRegistrant || '';
     console.log('选择品牌:', { 
       brandId: formData.value.brandId, 
+      brandNo: formData.value.brandNo,
       brandName: formData.value.brandName,
       brandLogo: formData.value.brandLogo,
       brandRegistrant: formData.value.brandRegistrant
@@ -1462,6 +1468,7 @@ const handleSubmit = async () => {
       supplierId: userStore.supplierId,
       supplierNo: userStore.supplierNo,
       brandId: formData.value.brandId,
+      brandNo: formData.value.brandNo,
       brandName: formData.value.brandName,
       brandLogo: formData.value.brandLogo,
       brandRegistrant: formData.value.brandRegistrant,

+ 13 - 5
src/views/supplier/author/index.vue

@@ -76,7 +76,7 @@
       </template>
 
       <!-- 授权详情信息列表 -->
-      <el-table v-loading="loading" :data="authorizationList" border style="width: 100%">
+      <el-table v-loading="loading" :data="authorizationList" border style="width: 100%" @row-dblclick="handleRowDblClick">
         <el-table-column prop="brandName" label="品牌名称" align="center" />
         <el-table-column label="一级类目" align="center">
           <template #default="scope">
@@ -127,7 +127,7 @@
         </el-table-column>
         <el-table-column label="操作" align="center" width="120">
           <template #default="scope">
-            <el-button link type="primary" @click="handleView(scope.row)">编辑</el-button>
+            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -501,9 +501,17 @@ const handleAdd = () => {
   router.push('/supplier/author/edit');
 };
 
-/** 查看/编辑 */
-const handleView = (row: any) => {
-  console.log('查看/编辑:', row);
+/** 行双击查看 */
+const handleRowDblClick = (row: any) => {
+  router.push({
+    path: '/supplier/author/detail',
+    query: { id: row.id }
+  });
+};
+
+/** 编辑 */
+const handleEdit = (row: any) => {
+  console.log('编辑:', row);
   router.push({
     path: '/supplier/author/edit',
     query: { id: row.id }

+ 51 - 0
src/views/supplier/info/components/AddressTab.vue

@@ -0,0 +1,51 @@
+<template>
+  <div class="tab-content">
+    <div class="info-section">
+      <div class="section-title-row">
+        <div class="section-title-left">
+          <span class="section-title-text">地址信息列表</span>
+        </div>
+        <el-button v-if="!isViewMode" icon="Plus" type="primary" @click="emit('add')">添加地址</el-button>
+      </div>
+      <el-table :data="addressList" border style="width: 100%">
+        <el-table-column prop="supplierNo" label="供应商编号" align="center" />
+        <el-table-column prop="addressNo" label="地址编号" align="center" />
+        <el-table-column prop="shipperName" label="姓名" align="center" />
+        <el-table-column prop="shipperPhone" label="手机号码" align="center" />
+        <el-table-column prop="shippingProvincial" label="省份" align="center" />
+        <el-table-column prop="shippingCity" label="市" align="center" />
+        <el-table-column prop="shippingCounty" label="区县" align="center" />
+        <el-table-column label="详细地址" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.shippingAddress || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="shippingPostCode" label="邮政编码" align="center" />
+        <el-table-column prop="isSelf" label="默认地址" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.isSelf === 1 || scope.row.isSelf === '1' ? '是' : '否' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="150">
+          <template #default="scope">
+            <el-button v-if="!isViewMode" link type="primary" @click="emit('edit', scope.row)">编辑</el-button>
+            <el-button v-if="!isViewMode" link type="danger" @click="emit('delete', scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+defineProps<{
+  addressList: any[];
+  isViewMode: boolean;
+}>();
+
+const emit = defineEmits<{
+  (e: 'add'): void;
+  (e: 'edit', row: any): void;
+  (e: 'delete', row: any): void;
+}>();
+</script>

+ 541 - 0
src/views/supplier/info/components/BasicInfoTab.vue

@@ -0,0 +1,541 @@
+<template>
+  <div class="tab-content">
+    <div class="info-section">
+      <div class="section-title-row">
+        <div class="section-title-left">
+          <span class="section-title-text">企业基本信息</span>
+          <span class="section-title-divider">/</span>
+          <span class="supplier-no">供应商编码:{{ detailData.supplierNo }}</span>
+          <el-tag 
+            v-if="detailData?.supplyStatus !== undefined && detailData?.supplyStatus !== null" 
+            :type="getStatusConfig(detailData.supplyStatus).type" 
+            effect="light" 
+            style="margin-left: 8px;"
+          >
+            {{ getStatusConfig(detailData.supplyStatus).text }}
+          </el-tag>
+        </div>
+        <div v-if="!isViewMode" class="section-title-actions">
+          <el-button type="primary" :icon="isEditing ? 'Document' : 'Edit'" @click="onPrimaryAction">
+            {{ isEditing ? '保存' : '编辑' }}
+          </el-button>
+          <el-button v-if="isEditing" @click="onCancelEdit">取消</el-button>
+        </div>
+      </div>
+
+      <el-form :model="detailData" label-width="120px" class="detail-form">
+        <el-row :gutter="12" class="form-row">
+          <el-col :span="8">
+            <el-form-item label="所属公司:" required>
+              <el-select v-model="detailData.ownedCompany" placeholder="请选择" clearable filterable style="width: 100%;" :disabled="!isAddMode || !isEditing">
+                <el-option v-for="company in companyOptions" :key="company.id" :label="company.companyName" :value="company.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="企业名称:" required>
+              <el-input v-model="detailData.enterpriseName" placeholder="请输入企业名称" :disabled="!isAddMode || !isEditing" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="工商名称:" required>
+              <el-input
+                v-model="detailData.businessName"
+                placeholder="请输入工商名称"
+                :disabled="!isEditing"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="12" class="form-row">
+          <el-col :span="8">
+            <el-form-item label="企业简称:" required>
+              <el-input v-model="detailData.shortName" placeholder="请输入企业简称" :disabled="!isEditing" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="供应商等级:" required>
+              <el-select v-model="detailData.cooperateLevel" placeholder="请选择" clearable filterable style="width: 100%;" :disabled="!isAddMode || !isEditing">
+                <el-option v-for="level in supplierLevelOptions" :key="level.id" :label="level.supplierLevelName" :value="String(level.id)" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="企业规模:" required>
+              <el-select v-model="detailData.membershipSize" placeholder="请选择" clearable filterable style="width: 100%;" :disabled="!isEditing">
+                <el-option v-for="scale in enterpriseScaleOptions" :key="scale.id" :label="scale.enterpriseScaleName" :value="scale.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="12" class="form-row">
+          <el-col :span="8">
+            <el-form-item label="行业类别:" required>
+              <el-select v-model="detailData.industrCategory" placeholder="请选择" clearable filterable style="width: 100%;" :disabled="!isAddMode || !isEditing">
+                <el-option v-for="industry in industryCategoryOptions" :key="industry.id" :label="industry.industryCategoryName" :value="industry.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="供应商类型:" required>
+              <el-select v-model="detailData.supplierType" placeholder="请选择" clearable filterable style="width: 100%;" :disabled="!isAddMode || !isEditing">
+                <el-option v-for="type in supplierTypeOptions" :key="type.id" :label="type.supplierTypeName" :value="type.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="固定电话:">
+              <el-input
+                v-model="detailData.fixedPhone"
+                placeholder="请输入固定电话"
+                type="tel"
+                @input="onFixedPhoneInput"
+                :disabled="!isEditing"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="12" class="form-row">
+          <el-col :span="8">
+            <el-form-item label="传真:">
+              <el-input v-model="detailData.fax" placeholder="请输入传真" :disabled="!isEditing" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="企业邮箱:">
+              <el-input v-model="detailData.mailbox" placeholder="请输入企业邮箱" :disabled="!isEditing" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="开始时间:">
+              <el-date-picker v-model="detailData.validityFromDate" type="date" placeholder="请选择" style="width: 100%;" :disabled="!isAddMode || !isEditing" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="12" class="form-row">
+          <el-col :span="8">
+            <el-form-item label="结束时间:">
+              <el-date-picker v-model="detailData.validityToDate" type="date" placeholder="请选择" style="width: 100%;" :disabled="!isAddMode || !isEditing" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="邮政编码:">
+              <el-input v-model="detailData.postCode" placeholder="请输入邮政编码" :disabled="!isEditing" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="网址:">
+              <el-input v-model="detailData.url" placeholder="请输入网址" :disabled="!isEditing" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="12" class="form-row">
+          <el-col :span="8">
+            <el-form-item label="详细地址:" required>
+              <el-cascader v-model="selectedOfficeRegionProxy" :options="regionOptions" placeholder="请选择省市区" clearable filterable style="width: 100%;" @change="onOfficeRegionChange" :disabled="!isEditing" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label=" " label-width="0">
+              <el-input v-model="detailData.officeAddress" placeholder="请输入详细地址" :disabled="!isEditing" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="12" class="form-row">
+          <el-col :span="8">
+            <el-form-item label="营业执照:">
+              <ImageUpload
+                v-model="detailData.businessLicense"
+                :limit="1"
+                :disabled="isViewMode || !isEditing"
+                :file-size="5"
+                :file-type="['png', 'jpg', 'jpeg']"
+                :is-show-tip="false"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="法人身份证照片:">
+              <ImageUpload
+                v-model="detailData.personImage"
+                :limit="1"
+                :disabled="isViewMode || !isEditing"
+                :file-size="5"
+                :file-type="['png', 'jpg', 'jpeg']"
+                :is-show-tip="false"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </div>
+
+    <div class="info-section">
+      <div class="section-title">工商信息</div>
+
+      <el-row :gutter="12" class="form-row">
+        <el-col :span="8">
+          <div class="form-item">
+            <span class="label">企业工商名称:</span>
+            <span class="value">{{ businessInfo?.businessName || detailData.businessName || '' }}</span>
+          </div>
+        </el-col>
+        <el-col :span="8">
+          <div class="form-item">
+            <span class="label">登记机关:</span>
+            <span class="value">{{ businessInfo?.registrationAuthority || '' }}</span>
+          </div>
+        </el-col>
+        <el-col :span="8">
+          <div class="form-item">
+            <span class="label">成立日期:</span>
+            <span class="value">{{ formatDate(businessInfo?.establishmentDate) || '' }}</span>
+          </div>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="12" class="form-row">
+        <el-col :span="8">
+          <div class="form-item">
+            <span class="label">登记状态:</span>
+            <span class="value">{{ businessInfo?.registrationStatus || '' }}</span>
+          </div>
+        </el-col>
+        <el-col :span="8">
+          <div class="form-item">
+            <span class="label">实缴资本:</span>
+            <span class="value">{{ businessInfo?.paidInCapital || '' }}</span>
+          </div>
+        </el-col>
+        <el-col :span="8">
+          <div class="form-item">
+            <span class="label">社会信用代码:</span>
+            <span class="value">{{ businessInfo?.socialCreditCode || detailData.socialCreditCode || '' }}</span>
+          </div>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="12" class="form-row">
+        <el-col :span="8">
+          <div class="form-item">
+            <span class="label">法人姓名:</span>
+            <span class="value">{{ businessInfo?.legalPersonName || '' }}</span>
+          </div>
+        </el-col>
+        <el-col :span="8">
+          <div class="form-item">
+            <span class="label">注册资本:</span>
+            <span class="value">{{ businessInfo?.registeredCapital || '' }}</span>
+          </div>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="12" class="form-row">
+        <el-col :span="24">
+          <div class="form-item">
+            <span class="label">工商地址:</span>
+            <el-input v-model="businessInfo.businessAddress" placeholder="工商地址" style="width: 70%;" :disabled="!isEditing" />
+          </div>
+        </el-col>
+      </el-row>
+
+    </div>
+
+    <div class="info-section">
+      <div class="section-title-row">
+        <div class="section-title-left">
+          <span class="section-title-text">付款信息</span>
+        </div>
+      </div>
+      <el-table :data="paymentInfoList" border style="width: 100%">
+        <el-table-column prop="isture" label="是否主账号" align="center">
+          <template #default="scope">
+            <span>{{ Number(scope.row.isture) === 1 ? '是' : '否' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="bankName" label="开户银行" align="center" />
+        <el-table-column prop="bankNo" label="银行账户" align="center" />
+      </el-table>
+
+
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import ImageUpload from '@/components/ImageUpload/index.vue';
+import { scmEditInfo } from '@/api/supplier/info';
+import { ref, computed, watch } from 'vue';
+import { useRoute } from 'vue-router';
+
+const props = defineProps<{
+  detailData: any;
+  companyOptions: any[];
+  enterpriseScaleOptions: any[];
+  industryCategoryOptions: any[];
+  supplierLevelOptions: any[];
+  supplierTypeOptions: any[];
+  isAddMode: boolean;
+  isViewMode: boolean;
+  isBasicInfoSaved: boolean;
+  businessInfo?: any;
+  businessInfoLoading?: boolean;
+  regionOptions: any[];
+  selectedOfficeRegion: string[];
+  paymentInfoList: any[];
+  formatDate: (date: any) => string;
+}>();
+
+const emit = defineEmits<{
+  (e: 'save'): void;
+  (e: 'officeRegionChange', value: string[]): void;
+  (e: 'update:selectedOfficeRegion', value: string[]): void;
+  (e: 'addPayment'): void;
+  (e: 'viewPayment', row: any): void;
+  (e: 'editPayment', row: any): void;
+}>();
+
+const selectedOfficeRegionProxy = computed({
+  get: () => props.selectedOfficeRegion,
+  set: (value) => emit('update:selectedOfficeRegion', value)
+});
+
+const onOfficeRegionChange = (val: unknown) => {
+  emit('officeRegionChange', (val || []) as string[]);
+};
+
+const onFixedPhoneInput = (val: string) => {
+  const next = (val || '').replace(/\D+/g, '');
+  if (next !== props.detailData.fixedPhone) {
+    props.detailData.fixedPhone = next;
+  }
+};
+
+const safeFormatDate = (date: any) => {
+  return typeof props.formatDate === 'function' ? props.formatDate(date) : '';
+};
+
+const isEditing = ref(false);
+
+const getStatusConfig = (status: string | number) => {
+  const s = String(status);
+  const map: Record<string, { text: string; type: 'success' | 'info' | 'warning' | 'primary' | 'danger' }> = {
+    '0': { text: '待审核', type: 'warning' },
+    '1': { text: '生效', type: 'success' },
+    '2': { text: '停止合作', type: 'danger' },
+    '3': { text: '审核不通过', type: 'danger' },
+    '4': { text: '待修改审核', type: 'warning' }
+  };
+  return map[s] || { text: '未知状态', type: 'info' };
+};
+
+const route = useRoute();
+const saveLoading = ref(false);
+const backupDetailData = ref<any>(null);
+const backupOfficeRegion = ref<string[] | null>(null);
+const backupBusinessAddress = ref<string | null>(null);
+const originDetailData = ref<any>(null);
+
+const makeSnapshot = (data: any) => {
+  try {
+    return JSON.parse(JSON.stringify(data ?? {}));
+  } catch (e) {
+    return data;
+  }
+};
+
+const getChangedFields = (origin: any, current: any) => {
+  const o = origin ?? {};
+  const c = current ?? {};
+  const changed: any = {};
+  Object.keys(c).forEach((k) => {
+    const ov = (o as any)[k];
+    const cv = (c as any)[k];
+    try {
+      if (JSON.stringify(ov) !== JSON.stringify(cv)) {
+        changed[k] = cv;
+      }
+    } catch (e) {
+      if (ov !== cv) {
+        changed[k] = cv;
+      }
+    }
+  });
+  return changed;
+};
+
+watch(
+  () => (props.detailData as any)?.id,
+  () => {
+    originDetailData.value = makeSnapshot(props.detailData);
+  },
+  { immediate: true }
+);
+
+const applySnapshot = (target: any, snapshot: any) => {
+  if (!target || !snapshot || typeof target !== 'object') return;
+  Object.keys(target).forEach((k) => {
+    delete target[k];
+  });
+  Object.assign(target, makeSnapshot(snapshot));
+};
+
+const onPrimaryAction = () => {
+  if (!isEditing.value) {
+    backupDetailData.value = makeSnapshot(props.detailData);
+    backupOfficeRegion.value = makeSnapshot(props.selectedOfficeRegion);
+    backupBusinessAddress.value = props.businessInfo?.businessAddress ?? null;
+    isEditing.value = true;
+    return;
+  }
+  if (saveLoading.value) return;
+  saveLoading.value = true;
+  const id = (route.query.id as string) || (props.detailData as any)?.id;
+  const changed = getChangedFields(originDetailData.value, props.detailData);
+  scmEditInfo({
+    ...changed,
+    id
+  } as any)
+    .then(() => {
+      originDetailData.value = makeSnapshot(props.detailData);
+      emit('save');
+      isEditing.value = false;
+    })
+    .finally(() => {
+      saveLoading.value = false;
+    });
+};
+
+const onCancelEdit = () => {
+  if (!isEditing.value) return;
+  applySnapshot(props.detailData, backupDetailData.value);
+  if (backupOfficeRegion.value) {
+    emit('update:selectedOfficeRegion', makeSnapshot(backupOfficeRegion.value));
+    emit('officeRegionChange', makeSnapshot(backupOfficeRegion.value));
+  }
+  if (props.businessInfo && backupBusinessAddress.value !== null) {
+    props.businessInfo.businessAddress = backupBusinessAddress.value;
+  }
+  isEditing.value = false;
+};
+</script>
+
+<style scoped>
+.tab-content {
+  --el-component-size: 24px;
+  padding: 16px;
+}
+
+.detail-form :deep(.el-form-item__label) {
+  white-space: nowrap;
+}
+
+.detail-form :deep(.form-row) {
+  margin-bottom: 2px;
+}
+
+.detail-form :deep(.el-form-item) {
+  margin-bottom: 8px;
+}
+
+.tab-content :deep(.form-row) {
+  margin-bottom: 2px;
+}
+
+.tab-content :deep(.el-form-item) {
+  margin-bottom: 8px;
+}
+
+.tab-content :deep(.info-section) {
+  margin-bottom: 20px;
+}
+
+.tab-content :deep(.section-title),
+.tab-content :deep(.section-title-row) {
+  margin-bottom: 12px;
+  padding-bottom: 8px;
+}
+
+.tab-content :deep(.section-title-text) {
+  font-size: 14px;
+}
+
+.tab-content :deep(.section-title-actions) {
+  display: flex;
+  gap: 8px;
+}
+
+.tab-content :deep(.supplier-no) {
+  font-size: 13px;
+}
+
+.tab-content :deep(.el-form-item__label) {
+  font-size: 13px;
+  height: var(--el-component-size);
+  line-height: var(--el-component-size);
+  padding-top: 0;
+  padding-bottom: 0;
+}
+
+.tab-content :deep(.form-item) {
+  line-height: 24px;
+  font-size: 13px;
+}
+
+.detail-form :deep(.el-input),
+.detail-form :deep(.el-select),
+.detail-form :deep(.el-date-editor),
+.detail-form :deep(.el-cascader) {
+  width: 100%;
+}
+
+.detail-form :deep(.el-input__wrapper),
+.detail-form :deep(.el-select__wrapper),
+.detail-form :deep(.el-date-editor),
+.detail-form :deep(.el-cascader) {
+  min-height: var(--el-component-size);
+}
+
+.detail-form :deep(.el-input__inner) {
+  height: var(--el-component-size);
+  line-height: var(--el-component-size);
+}
+
+.detail-form :deep(.el-form-item__content) {
+  align-items: center;
+  min-height: var(--el-component-size);
+}
+
+.detail-form :deep(.el-input__wrapper),
+.detail-form :deep(.el-select__wrapper),
+.detail-form :deep(.el-date-editor),
+.detail-form :deep(.el-cascader) {
+  height: var(--el-component-size);
+  align-items: center;
+  padding-top: 0;
+  padding-bottom: 0;
+}
+
+.detail-form :deep(.el-select__selected-item),
+.detail-form :deep(.el-select__input),
+.detail-form :deep(.el-range-input) {
+  line-height: var(--el-component-size);
+}
+
+:deep(.component-upload-image .el-upload--picture-card),
+:deep(.component-upload-image .el-upload-list--picture-card .el-upload-list__item) {
+  width: 64px;
+  height: 64px;
+}
+
+:deep(.component-upload-image .el-upload-list--picture-card .el-upload-list__item-thumbnail) {
+  width: 64px;
+  height: 64px;
+}
+</style>

+ 56 - 0
src/views/supplier/info/components/ContactTab.vue

@@ -0,0 +1,56 @@
+<template>
+  <div class="tab-content">
+    <div class="info-section">
+      <div class="section-title-row">
+        <div class="section-title-left">
+          <span class="section-title-text">业务联系人</span>
+        </div>
+        <el-button v-if="!isViewMode" type="primary" icon="Plus" @click="emit('add')">新增联系人</el-button>
+      </div>
+
+      <el-table v-loading="contactLoading" :data="contactList" border style="width: 100%">
+        
+        <el-table-column prop="userNo" label="用户ID" align="center" />
+        
+        <el-table-column prop="userName" label="员工姓名" align="center" />
+        <el-table-column prop="phone" label="手机号" align="center" />
+        <el-table-column prop="roleNo" label="角色" align="center" />
+        <el-table-column prop="departmentNo" label="部门" align="center" />
+        <el-table-column prop="position" label="职位" align="center" />
+        <el-table-column prop="isPrimaryContact" label="主要联系人" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.isPrimaryContact === '1' ? '是' : '否' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="isRegister" label="允许登录供应商端" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.isRegister === '1' ? '是' : '否' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center">
+          <template #default="scope">
+            <el-button v-if="!isViewMode" link type="primary" @click="emit('edit', scope.row)">编辑</el-button>
+            <el-button v-if="!isViewMode" link type="primary" @click="emit('resetPassword', scope.row)">重置密码</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+defineProps<{
+  contactSearchParams: { userNo: string; userName: string };
+  contactList: any[];
+  contactLoading: boolean;
+  isViewMode: boolean;
+}>();
+
+const emit = defineEmits<{
+  (e: 'search'): void;
+  (e: 'reset'): void;
+  (e: 'add'): void;
+  (e: 'edit', row: any): void;
+  (e: 'resetPassword', row: any): void;
+}>();
+</script>

+ 120 - 0
src/views/supplier/info/components/ContractTab.vue

@@ -0,0 +1,120 @@
+<template>
+  <div class="tab-content">
+    <div class="info-section">
+      <el-form :model="contractSearchParams" label-width="70px" style="margin-bottom: 20px;">
+        <el-row :gutter="16">
+          <el-col :span="6">
+            <el-form-item label="合同编号">
+              <el-input v-model="contractSearchParams.contractNo" placeholder="请输入合同编号" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="合同名称">
+              <el-input v-model="contractSearchParams.contractName" placeholder="请输入合同名称" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="合同类型">
+              <el-select v-model="contractSearchParams.contractType" placeholder="请选择" clearable filterable style="width: 100%;">
+                <el-option v-for="item in contractTypeDict" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-form-item label-width="0">
+              <el-button type="primary" icon="Search" @click="emit('search')">搜索</el-button>
+              <el-button icon="Refresh" @click="emit('reset')">重置</el-button>
+            </el-form-item>
+          
+        </el-row>
+
+        
+      </el-form>
+
+      <div class="section-title">合同信息列表</div>
+
+      <el-table :data="contractList" border style="width: 100%">
+        <el-table-column prop="contractNo" label="合同编号" align="center" />
+        <el-table-column prop="contractName" label="合同名称" align="center" />
+        <el-table-column prop="contractType" label="合同类型" align="center">
+          <template #default="scope">
+            <span>{{ getContractTypeText(scope.row.contractType) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="contractAmount" label="金额(万)" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.contractAmount || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="contractStartTime" label="起始时间" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.contractStartTime ? formatDate(scope.row.contractStartTime) : '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="contractEndTime" label="截止时间" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.contractEndTime ? formatDate(scope.row.contractEndTime) : '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createTime" label="上传时间" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.createTime ? formatDate(scope.row.createTime) : '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="contractAttachment" label="附件管理" align="center">
+          <template #default="scope">
+            <el-button v-if="scope.row.contractAttachment" link type="primary" @click="emit('viewAttachment', scope.row)">查看附件</el-button>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="contractStatus" label="状态" align="center">
+          <template #default="scope">
+            <span>{{ getContractStatusText(scope.row.contractStatus) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="120">
+          <template #default="scope">
+            <el-button link type="primary" @click="emit('view', scope.row)">查看</el-button>
+           <!--  <el-button v-if="!isViewMode" link type="primary" @click="emit('edit', scope.row)">编辑</el-button> -->
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div style="margin-top: 20px; display: flex; justify-content: flex-end;">
+        <el-pagination
+          v-model:current-page="contractPagination.pageNum"
+          v-model:page-size="contractPagination.pageSize"
+          :page-sizes="[10, 20, 50, 100]"
+          :total="contractPagination.total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="emit('sizeChange', $event)"
+          @current-change="emit('currentChange', $event)"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+defineProps<{
+  contractSearchParams: any;
+  contractList: any[];
+  contractPagination: { pageNum: number; pageSize: number; total: number };
+  isViewMode: boolean;
+  contractTypeDict: any[];
+  contractStatusDict: any[];
+  formatDate: (date: any) => string;
+  getContractTypeText: (type: any) => string;
+  getContractStatusText: (status: any) => string;
+}>();
+
+const emit = defineEmits<{
+  (e: 'search'): void;
+  (e: 'reset'): void;
+  (e: 'add'): void;
+  (e: 'viewAttachment', row: any): void;
+  (e: 'view', row: any): void;
+  (e: 'edit', row: any): void;
+  (e: 'sizeChange', size: number): void;
+  (e: 'currentChange', page: number): void;
+}>();
+</script>

+ 53 - 0
src/views/supplier/info/components/PurchaseInfoTab.vue

@@ -0,0 +1,53 @@
+<template>
+  <div class="tab-content">
+    <div class="info-section">
+      <div class="section-title" style="display: flex; align-items: center; justify-content: space-between;">
+        <span class="purchase-title">采购信息</span>
+      </div>
+
+      <el-row :gutter="40" class="form-row">
+        <el-col :span="12">
+          <div class="form-item">
+            <span class="label">产品经理:</span>
+            <span>{{ productManagerName }}</span>
+          </div>
+        </el-col>
+        <el-col :span="12">
+          <div class="form-item">
+            <span class="label">采购员:</span>
+            <span>{{ buyerName }}</span>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue';
+
+const props = defineProps<{
+  staffOptions: any[];
+  selectedProductManager: number | null;
+  selectedBuyer: number | null;
+  isViewMode: boolean;
+}>();
+
+const productManagerName = computed(() => {
+  const id = props.selectedProductManager;
+  const item = props.staffOptions?.find((x: any) => x?.staffId === id);
+  return item?.displayText || '';
+});
+
+const buyerName = computed(() => {
+  const id = props.selectedBuyer;
+  const item = props.staffOptions?.find((x: any) => x?.staffId === id);
+  return item?.displayText || '';
+});
+</script>
+
+<style scoped>
+.purchase-title {
+  color: #409eff;
+}
+</style>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 698 - 218
src/views/supplier/info/index.vue


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است