Эх сурвалжийг харах

Merge branch 'master' of http://8.152.4.3:3000/yp_web/yoe-srm-web

hurx 1 сар өмнө
parent
commit
c502608b01

+ 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) : '';

+ 523 - 0
src/components/RegionCascader/index.vue

@@ -0,0 +1,523 @@
+<template>
+  <div class="region-cascader">
+    <div class="region-selector">
+      <!-- 左侧:省份列表 -->
+      <div class="region-list province-list">
+        <div class="list-header">省份</div>
+        <div class="list-body">
+          <div 
+            v-for="province in provinces" 
+            :key="province.value"
+            class="region-item"
+            :class="{ 'is-active': isProvinceSelected(province.value), 'is-checked': getProvinceCheckState(province).checked || getProvinceCheckState(province).indeterminate }"
+            @click="handleProvinceClick(province)"
+          >
+            <el-checkbox 
+              v-if="multiple"
+              :model-value="getProvinceCheckState(province).checked"
+              :indeterminate="getProvinceCheckState(province).indeterminate"
+              @click.stop
+              @change="handleProvinceCheck(province, $event)"
+            >
+              {{ province.label }}
+            </el-checkbox>
+            <span v-else>{{ province.label }}</span>
+            <el-icon v-if="province.children && province.children.length > 0" class="arrow-icon">
+              <ArrowRight />
+            </el-icon>
+          </div>
+        </div>
+      </div>
+      
+      <!-- 中间:城市列表 -->
+      <div class="region-list city-list">
+        <div class="list-header">城市</div>
+        <div class="list-body">
+          <div 
+            v-for="city in currentCities" 
+            :key="city.value"
+            class="region-item"
+            :class="{ 'is-active': isCitySelected(city.value), 'is-checked': isCityChecked(city.value) }"
+            @click="handleCityClick(city)"
+          >
+            <el-checkbox 
+              v-if="multiple"
+              :model-value="isCityChecked(city.value)"
+              @click.stop
+              @change="handleCityCheck(city, $event)"
+            >
+              {{ city.label }}
+            </el-checkbox>
+            <span v-else>{{ city.label }}</span>
+            <el-icon v-if="showDistrict && city.children && city.children.length > 0" class="arrow-icon">
+              <ArrowRight />
+            </el-icon>
+          </div>
+          <div v-if="currentCities.length === 0" class="empty-text">
+            请先选择省份
+          </div>
+        </div>
+      </div>
+      
+      <!-- 右侧:区县列表 -->
+      <div v-if="showDistrict" class="region-list district-list">
+        <div class="list-header">区县</div>
+        <div class="list-body">
+          <div 
+            v-for="district in currentDistricts" 
+            :key="district.value"
+            class="region-item"
+            :class="{ 'is-checked': isDistrictChecked(district.value) }"
+            @click="handleDistrictClick(district)"
+          >
+            <el-checkbox 
+              v-if="multiple"
+              :model-value="isDistrictChecked(district.value)"
+              @change="handleDistrictCheck(district, $event)"
+            >
+              {{ district.label }}
+            </el-checkbox>
+            <span v-else>{{ district.label }}</span>
+          </div>
+          <div v-if="currentDistricts.length === 0" class="empty-text">
+            请先选择城市
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, watch } from 'vue';
+import { ArrowRight } from '@element-plus/icons-vue';
+
+interface RegionData {
+  label: string;
+  value: string;
+  children?: RegionData[];
+}
+
+interface Props {
+  modelValue?: string[]; // 选中的区域代码数组
+  multiple?: boolean; // 是否多选模式,默认 true
+  options?: RegionData[]; // 省市区数据源(建议由后端接口提供)
+  showDistrict?: boolean; // 是否展示区县列(供货区域可关闭,仅省/市两级)
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: string[]): void;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  modelValue: () => [],
+  multiple: true,
+  options: () => [],
+  showDistrict: true
+});
+
+const emit = defineEmits<Emits>();
+
+// 所有省份
+const provinces = ref<RegionData[]>(props.options || []);
+
+// 当前选中的省份
+const currentProvince = ref<RegionData | null>(null);
+
+// 当前选中的城市
+const currentCity = ref<RegionData | null>(null);
+
+// 当前省份下的城市列表
+const currentCities = computed(() => {
+  return currentProvince.value?.children || [];
+});
+
+// 当前城市下的区县列表
+const currentDistricts = computed(() => {
+  return currentCity.value?.children || [];
+});
+
+// 选中的区域代码
+const selectedRegions = ref<string[]>(props.modelValue || []);
+
+// 仅省/市模式下:记录“手动勾选”的省份(用于判断取消城市时是否自动取消父省)
+const manualCheckedProvinces = ref<Set<string>>(new Set());
+
+// 监听 props 变化
+watch(() => props.modelValue, (newVal) => {
+  selectedRegions.value = newVal || [];
+
+  if (!props.showDistrict) {
+    // 在省/市模式下,v-model 会同时包含省ID和市ID。
+    // manualCheckedProvinces 仅用于区分“用户手动点省”与“由市联动带上省”。
+    // 这里不做强推断,避免误判;仅在 handleProvinceCheck 中维护。
+  }
+}, { immediate: true });
+
+watch(() => props.options, (newVal) => {
+  provinces.value = (newVal || []) as RegionData[];
+  // 切换数据源后重置当前选中路径
+  currentProvince.value = null;
+  currentCity.value = null;
+}, { immediate: true });
+
+/** 判断省份是否被选中(显示箭头高亮) */
+const isProvinceSelected = (provinceCode: string) => {
+  return currentProvince.value?.value === provinceCode;
+};
+
+/** 判断省份是否被勾选 */
+const isProvinceChecked = (provinceCode: string) => {
+  if (!props.showDistrict) {
+    return selectedRegions.value.includes(provinceCode);
+  }
+  const province = provinces.value.find(p => p.value === provinceCode);
+  if (!province || !province.children) return false;
+  
+  // 递归检查该省份下所有区县是否都被选中
+  const allDistrictCodes: string[] = [];
+  province.children.forEach(city => {
+    if (city.children) {
+      city.children.forEach(district => {
+        allDistrictCodes.push(district.value);
+      });
+    }
+  });
+  
+  return allDistrictCodes.length > 0 && allDistrictCodes.every(code => selectedRegions.value.includes(code));
+};
+
+/** 判断城市是否被选中(显示箭头高亮) */
+const isCitySelected = (cityCode: string) => {
+  return currentCity.value?.value === cityCode;
+};
+
+/** 判断城市是否被勾选 */
+const isCityChecked = (cityCode: string) => {
+  if (!props.showDistrict) {
+    return selectedRegions.value.includes(cityCode);
+  }
+  const city = currentCities.value.find(c => c.value === cityCode);
+  if (!city || !city.children) return false;
+  
+  // 检查该城市下所有区县是否都被选中
+  return city.children.every(district => selectedRegions.value.includes(district.value));
+};
+
+/** 判断区县是否被勾选 */
+const isDistrictChecked = (districtCode: string) => {
+  return selectedRegions.value.includes(districtCode);
+};
+
+const findProvinceByCityValue = (cityValue: string): RegionData | null => {
+  for (const p of provinces.value || []) {
+    const cities = p.children || [];
+    if (cities.some((c) => c.value === cityValue)) return p;
+  }
+  return null;
+};
+
+const hasAnySelectedCityUnderProvince = (provinceValue: string) => {
+  const province = (provinces.value || []).find((p) => p.value === provinceValue);
+  if (!province) return false;
+  const cities = province.children || [];
+  return cities.some((c) => selectedRegions.value.includes(c.value));
+};
+
+const getProvinceCheckState = (province: RegionData) => {
+  if (!props.showDistrict) {
+    const cities = province.children || [];
+    const total = cities.length;
+    const selectedCount = cities.filter((c) => selectedRegions.value.includes(c.value)).length;
+
+    const checked = total > 0 ? selectedCount === total : selectedRegions.value.includes(province.value);
+    const indeterminate = total > 0 ? selectedCount > 0 && selectedCount < total : false;
+
+    return { checked, indeterminate };
+  }
+
+  return { checked: isProvinceChecked(province.value), indeterminate: false };
+};
+
+/** 点击省份 */
+const handleProvinceClick = (province: RegionData) => {
+  currentProvince.value = province;
+  currentCity.value = null; // 重置城市选择
+};
+
+/** 点击城市 */
+const handleCityClick = (city: RegionData) => {
+  currentCity.value = city;
+  // 单选模式下不做选中操作
+  if (!props.multiple) {
+    // 单选模式下只展开城市,不自动选择
+  }
+};
+
+/** 点击区县(单选模式) */
+const handleDistrictClick = (district: RegionData) => {
+  if (!props.multiple) {
+    // 单选模式:清空其他选中,只选中当前区县
+    selectedRegions.value = [district.value];
+    emit('update:modelValue', selectedRegions.value);
+  }
+};
+
+/** 勾选/取消省份 */
+const handleProvinceCheck = (province: RegionData, checked: boolean | string | number) => {
+  if (!props.showDistrict) {
+    const isChecked = !!checked;
+    const next = new Set(selectedRegions.value);
+
+    const cities = province.children || [];
+
+    if (isChecked) {
+      next.add(province.value);
+      manualCheckedProvinces.value.add(province.value);
+
+      // 省勾选:该省下所有市一起勾选
+      cities.forEach((c) => next.add(c.value));
+    } else {
+      // 省取消:该省下所有市一起取消
+      next.delete(province.value);
+      manualCheckedProvinces.value.delete(province.value);
+
+      cities.forEach((c) => next.delete(c.value));
+    }
+
+    selectedRegions.value = Array.from(next);
+    emit('update:modelValue', selectedRegions.value);
+    return;
+  }
+
+  const isChecked = !!checked;
+  if (!province.children) return;
+  
+  // 获取该省份下所有区县代码
+  const districtCodes: string[] = [];
+  province.children.forEach(city => {
+    if (city.children) {
+      city.children.forEach(district => {
+        districtCodes.push(district.value);
+      });
+    }
+  });
+  
+  if (isChecked) {
+    // 添加该省份下所有区县
+    const newRegions = [...selectedRegions.value];
+    districtCodes.forEach(code => {
+      if (!newRegions.includes(code)) {
+        newRegions.push(code);
+      }
+    });
+    selectedRegions.value = newRegions;
+  } else {
+    // 移除该省份下所有区县
+    selectedRegions.value = selectedRegions.value.filter(code => !districtCodes.includes(code));
+  }
+  
+  emit('update:modelValue', selectedRegions.value);
+};
+
+/** 勾选/取消城市 */
+const handleCityCheck = (city: RegionData, checked: boolean | string | number) => {
+  if (!props.showDistrict) {
+    const isChecked = !!checked;
+    const next = new Set(selectedRegions.value);
+
+    if (isChecked) {
+      next.add(city.value);
+
+      const parentProvince = findProvinceByCityValue(city.value);
+      if (parentProvince) {
+        next.add(parentProvince.value);
+      }
+    } else {
+      next.delete(city.value);
+
+      const parentProvince = findProvinceByCityValue(city.value);
+      if (parentProvince) {
+        const provinceValue = parentProvince.value;
+        // 若该省下已没有任何选中的城市,则自动取消父省
+        selectedRegions.value = Array.from(next);
+        if (!hasAnySelectedCityUnderProvince(provinceValue)) {
+          next.delete(provinceValue);
+          manualCheckedProvinces.value.delete(provinceValue);
+        }
+      }
+    }
+
+    selectedRegions.value = Array.from(next);
+    emit('update:modelValue', selectedRegions.value);
+    return;
+  }
+
+  const isChecked = !!checked;
+  if (!city.children) return;
+  
+  const districtCodes = city.children.map(district => district.value);
+  
+  if (isChecked) {
+    // 添加该城市下所有区县
+    const newRegions = [...selectedRegions.value];
+    districtCodes.forEach(code => {
+      if (!newRegions.includes(code)) {
+        newRegions.push(code);
+      }
+    });
+    selectedRegions.value = newRegions;
+  } else {
+    // 移除该城市下所有区县
+    selectedRegions.value = selectedRegions.value.filter(code => !districtCodes.includes(code));
+  }
+  
+  emit('update:modelValue', selectedRegions.value);
+};
+
+/** 勾选/取消区县 */
+const handleDistrictCheck = (district: RegionData, checked: boolean | string | number) => {
+  const isChecked = !!checked;
+  if (isChecked) {
+    selectedRegions.value.push(district.value);
+  } else {
+    selectedRegions.value = selectedRegions.value.filter(code => code !== district.value);
+  }
+  
+  emit('update:modelValue', selectedRegions.value);
+};
+
+/** 获取选中的区域名称列表 */
+const getSelectedRegionNames = () => {
+  const names: string[] = [];
+  
+  provinces.value.forEach(province => {
+    if (province.children) {
+      province.children.forEach(city => {
+        if (city.children) {
+          city.children.forEach(district => {
+            if (selectedRegions.value.includes(district.value)) {
+              names.push(`${province.label}-${city.label}-${district.label}`);
+            }
+          });
+        }
+      });
+    }
+  });
+  
+  return names;
+};
+
+// 暴露方法给父组件
+defineExpose({
+  getSelectedRegionNames
+});
+</script>
+
+<style scoped>
+.region-cascader {
+  width: 100%;
+}
+
+.region-selector {
+  display: flex;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  height: 400px;
+  overflow: hidden;
+}
+
+.region-list {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  border-right: 1px solid #dcdfe6;
+  min-width: 200px;
+}
+
+.region-list:last-child {
+  border-right: none;
+}
+
+.list-header {
+  padding: 12px 16px;
+  background: #f5f7fa;
+  border-bottom: 1px solid #dcdfe6;
+  font-weight: 500;
+  font-size: 14px;
+  color: #303133;
+}
+
+.list-body {
+  flex: 1;
+  overflow-y: auto;
+  padding: 8px 0;
+}
+
+.region-item {
+  padding: 8px 16px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  transition: background-color 0.2s;
+}
+
+.region-item:hover {
+  background: #f5f7fa;
+}
+
+.region-item.is-active {
+  background: #ecf5ff;
+  color: #409eff;
+}
+
+.region-item.is-checked {
+  font-weight: 500;
+}
+
+.arrow-icon {
+  color: #909399;
+  font-size: 12px;
+  margin-left: 8px;
+}
+
+.region-item.is-active .arrow-icon {
+  color: #409eff;
+}
+
+.empty-text {
+  padding: 20px;
+  text-align: center;
+  color: #909399;
+  font-size: 14px;
+}
+
+/* 复选框样式调整 */
+.region-item :deep(.el-checkbox) {
+  width: 100%;
+}
+
+.region-item :deep(.el-checkbox__label) {
+  flex: 1;
+  padding-left: 8px;
+}
+
+/* 滚动条样式 */
+.list-body::-webkit-scrollbar {
+  width: 6px;
+}
+
+.list-body::-webkit-scrollbar-thumb {
+  background: #dcdfe6;
+  border-radius: 3px;
+}
+
+.list-body::-webkit-scrollbar-thumb:hover {
+  background: #c0c4cc;
+}
+
+.list-body::-webkit-scrollbar-track {
+  background: transparent;
+}
+</style>

+ 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>

+ 1738 - 0
src/views/supplier/author/detail.vue

@@ -0,0 +1,1738 @@
+<template>
+  <div class="app-container">
+    <!-- 页面头部 -->
+    <div class="page-header">
+      <el-icon class="back-icon" @click="goBack"><ArrowLeft /></el-icon>
+      <span class="page-title">查看授权</span>
+    </div>
+
+    <!-- 设置授权 -->
+    <div class="form-section">
+      <div class="section-title">设置授权</div>
+      
+      <el-form :model="formData" label-width="100px" class="auth-form">
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="品牌名称:">
+              <el-select
+                v-if="!isEditMode"
+                v-model="formData.brandName"
+                placeholder="请选择"
+                clearable
+                filterable
+                style="width: 100%;"
+                @change="handleBrandChange"
+                @clear="handleBrandClear"
+              >
+                <el-option
+                  v-for="brand in brandList"
+                  :key="brand.id"
+                  :label="brand.brandName"
+                  :value="brand.brandName"
+                />
+              </el-select>
+              <el-input
+                v-else
+                v-model="formData.brandName"
+                readonly
+                style="width: 100%;"
+                placeholder="品牌名称"
+                class="readonly-input"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="商标注册人:">
+              <el-input v-model="formData.brandRegistrant" readonly style="width: 100%;" placeholder="商标注册人" class="readonly-input readonly-not-allowed" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="授权类型:">
+              <el-select 
+                v-model="formData.authorizeTypeId" 
+                placeholder="请选择" 
+                clearable 
+                style="width: 100%;"
+                @change="handleAuthorizeTypeChange"
+              >
+                <el-option
+                  v-for="item in authorizeTypeList"
+                  :key="item.id"
+                  :label="item.authorizeType"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          
+        </el-row>
+
+        <!-- 第一行:三个分类下拉框 -->
+        <el-row :gutter="20">
+          <el-col :span="6">
+            <el-form-item label="选择分类:">
+              <el-select
+                v-if="!isEditMode"
+                v-model="formData.category1"
+                placeholder="请选择"
+                clearable
+                style="width: 100%;"
+                @change="handleCategory1Change"
+              >
+                <el-option
+                  v-for="item in category1List"
+                  :key="item.id"
+                  :label="item.categoryName"
+                  :value="item.id"
+                />
+              </el-select>
+              <el-input
+                v-else
+                :value="categoryDisplayNames.oneLevelName"
+                readonly
+                style="width: 100%;"
+                placeholder="一级分类"
+                class="readonly-input"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label=" ">
+              <el-select
+                v-if="!isEditMode"
+                v-model="formData.category2"
+                placeholder="请选择"
+                clearable
+                style="width: 100%;"
+                :disabled="!formData.category1"
+                @change="handleCategory2Change"
+              >
+                <el-option
+                  v-for="item in category2List"
+                  :key="item.id"
+                  :label="item.categoryName"
+                  :value="item.id"
+                />
+              </el-select>
+              <el-input
+                v-else
+                :value="categoryDisplayNames.twoLevelName"
+                readonly
+                style="width: 100%;"
+                placeholder="二级分类"
+                class="readonly-input"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label=" ">
+              <el-select
+                v-if="!isEditMode"
+                v-model="formData.category3"
+                placeholder="请选择"
+                clearable
+                style="width: 100%;"
+                :disabled="!formData.category2"
+                @change="handleCategory3Change"
+              >
+                <el-option
+                  v-for="item in category3List"
+                  :key="item.id"
+                  :label="item.categoryName"
+                  :value="item.id"
+                />
+              </el-select>
+              <el-input
+                v-else
+                :value="categoryDisplayNames.threeLevelName"
+                readonly
+                style="width: 100%;"
+                placeholder="三级分类"
+                class="readonly-input"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-button v-if="!isEditMode" type="primary" icon="Plus" @click="handleAddCategory">添加分类</el-button>
+          </el-col>
+        </el-row>
+
+        <!-- 额外添加的分类行(只有二级和三级) -->
+        <div v-for="row in extraCategoryRows" :key="row.id" class="extra-category-row">
+          <el-row :gutter="20">
+            <el-col :span="6">
+              <el-form-item label=" ">
+                <!-- 占位,保持对齐 -->
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label=" ">
+                <el-select
+                  v-model="row.category2"
+                  placeholder="请选择"
+                  clearable
+                  style="width: 100%;"
+                  @change="(val) => handleExtraCategory2Change(row, val)"
+                >
+                  <el-option
+                    v-for="item in row.category2List"
+                    :key="item.id"
+                    :label="item.categoryName"
+                    :value="item.id"
+                  />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label=" ">
+                <el-select
+                  v-model="row.category3"
+                  placeholder="请选择"
+                  clearable
+                  style="width: 100%;"
+                  :disabled="!row.category2"
+                  @change="(val) => handleExtraCategory3Change(row, val)"
+                >
+                  <el-option
+                    v-for="item in row.category3List"
+                    :key="item.id"
+                    :label="item.categoryName"
+                    :value="item.id"
+                  />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-button type="danger" icon="Delete" @click="handleDeleteCategory(row)">删除分类</el-button>
+            </el-col>
+          </el-row>
+        </div>
+
+        <!-- 授权链路 -->
+        <el-form-item label="授权链路:">
+          <div class="auth-chain-container">
+            <!-- 授权链路自定义表格 -->
+            <div class="auth-chain-custom-table">
+              <div class="auth-chain-header">
+                <div 
+                  v-for="level in authChainLevels" 
+                  :key="'h-' + level.id"
+                  class="auth-chain-cell header-cell"
+                >
+                  {{ level.label }}
+                </div>
+              </div>
+              <div class="auth-chain-row">
+                <div 
+                  v-for="level in authChainLevels" 
+                  :key="'c-' + level.id"
+                  class="auth-chain-cell"
+                >
+                  <el-input 
+                    v-if="level.editable"
+                    v-model="level.value" 
+                    :placeholder="level.placeholder"
+                    clearable
+                  />
+                  <span v-else>{{ level.value }}</span>
+                </div>
+              </div>
+            </div>
+            
+          </div>
+        </el-form-item>
+
+        <!-- 原来的授权人员输入(保留或删除,根据需求) -->
+        <!-- <el-form-item>
+          <el-input
+            v-model="formData.authPerson"
+            placeholder="请输入品牌授权人"
+            style="width: 300px;"
+          />
+          <el-button type="primary" icon="Plus" style="margin-left: 10px;" @click="handleAddPerson">
+            添加品牌授权人
+          </el-button>
+          <el-button icon="Delete" @click="handleDeletePerson">删除</el-button>
+        </el-form-item> -->
+      </el-form>
+
+      <!-- 资质文件 -->
+      <div class="file-section">
+        <div class="file-title">资质文件</div>
+        <el-table :data="fileList" border style="width: 100%">
+          <el-table-column prop="index" label="序号" align="center" width="80" />
+          <el-table-column prop="typeName" label="资质名称" align="center" width="150" />
+          <el-table-column label="文件名称" align="center" min-width="400">
+            <template #default="scope">
+              <div v-if="scope.row.fileOssIds" class="file-name-list">
+                <div v-for="(fileName, idx) in getFileNames(scope.row.fileOssIds)" :key="idx" class="file-name-item">
+                  {{ fileName }}
+                </div>
+              </div>
+              <span v-else style="color: #999;">暂无文件</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="资质文件" align="center" width="250">
+            <template #default="scope">
+              <div class="file-actions">
+                <template v-if="scope.row.fileOssIds">
+                  <el-button type="primary" link @click="handleDownloadFile(scope.row)">下载</el-button>
+                </template>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="资质到期日" align="center" width="220">
+            <template #default="scope">
+              <el-date-picker
+                v-model="scope.row.expireDate"
+                type="date"
+                placeholder="请选择到期日"
+                style="width: 100%;"
+              />
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+    <!-- 文件上传对话框 -->
+    <el-dialog
+      v-model="fileUploadDialogVisible"
+      title="上传资质文件"
+      width="600px"
+      :close-on-click-modal="false"
+    >
+      <FileUpload 
+        v-model="currentFileRow.fileOssIds"
+        :limit="5"
+        :file-size="10"
+        :file-type="['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx']"
+      />
+      <template #footer>
+        <el-button @click="fileUploadDialogVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+
+      <!-- 授权区域 -->
+      <div class="area-section">
+        <div class="area-title">
+          <span>授权区域:</span>
+        </div>
+        <el-table :data="areaList" border style="width: 100%">
+          <el-table-column type="index" label="序号" align="center" width="80" />
+          <el-table-column prop="province" label="授权区域(省)" align="center" />
+          <el-table-column prop="city" label="授权区域(市)" align="center" />
+        </el-table>
+        
+      </div>
+
+      <!-- 提交按钮 -->
+    </div>
+
+    <!-- 授权区域选择对话框 -->
+    <el-dialog
+      v-model="areaDialogVisible"
+      title="选择授权区域"
+      width="700px"
+      :close-on-click-modal="false"
+    >
+      <el-cascader
+        v-model="selectedAreas"
+        :options="areaOptions"
+        :props="cascaderProps"
+        placeholder="请选择授权区域"
+        style="width: 100%;"
+        clearable
+        filterable
+      />
+      <template #footer>
+        <el-button @click="areaDialogVisible = false">取消</el-button>
+        <el-button type="primary" :loading="areaSubmitLoading" @click="handleAreaSubmit">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, watch } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import { ArrowLeft } from '@element-plus/icons-vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { listBrand } from '@/api/product/brand';
+import { listCategory } from '@/api/product/category';
+import type { CategoryVO } from '@/api/product/category/types';
+import { listByIds } from '@/api/system/oss';
+import { addSupplierauthorize, getBrandAuthorizeDetail, updateSupplierauthorize } from '@/api/supplier/supplierauthorize';
+import type { SupplierauthorizeForm } from '@/api/supplier/supplierauthorize/types';
+import type { QualificationFileForm } from '@/api/supplier/qualificationFile/types';
+import { listAuthorizetypeLevel } from '@/api/supplier/authorizetypeLevel';
+import type { AuthorizetypeLevelVO } from '@/api/supplier/authorizetypeLevel/types';
+import { useUserStore } from '@/store/modules/user';
+import FileUpload from '@/components/FileUpload/index.vue';
+import { regionData } from 'element-china-area-data';
+
+const router = useRouter();
+const route = useRoute();
+const userStore = useUserStore();
+
+// 判断是新增还是编辑模式
+const editId = ref<string | number>('');
+const isEditMode = ref(false);
+
+// 分类显示名称(编辑模式用)
+const categoryDisplayNames = ref({
+  oneLevelName: '',
+  twoLevelName: '',
+  threeLevelName: ''
+});
+
+// 表单数据
+const formData = ref({
+  brandId: '',
+  brandNo: '',
+  brandName: '',
+  brandLogo: '', // 品牌LOGO
+  brandRegistrant: '', // 商标注册人
+  authorizeTypeId: '', // 授权类型ID
+  authorizeLevel: '', // 授权等级
+  category1: '', // 第一行的一级分类
+  category2: '', // 第一行的二级分类
+  category3: '', // 第一行的三级分类
+  authPerson: ''
+});
+
+// 品牌列表
+const brandList = ref<any[]>([]);
+
+// 授权类型列表
+const authorizeTypeList = ref<AuthorizetypeLevelVO[]>([]);
+
+// 授权链路
+interface AuthChainLevel {
+  id: number;         // 唯一标识
+  label: string;      // 列标题(0级授权、1级授权...)
+  value: string;      // 授权人名称
+  editable: boolean;  // 是否可编辑
+  placeholder: string; // 输入框提示
+}
+
+let authLevelIdCounter = 0;
+
+const authChainLevels = ref<AuthChainLevel[]>([
+  {
+    id: authLevelIdCounter++,
+    label: '0级授权',
+    value: '',
+    editable: true,
+    placeholder: '请输入授权人'
+  },
+  {
+    id: authLevelIdCounter++,
+    label: '1级授权',
+    value: '优易达(武汉)有限公司',
+    editable: false,
+    placeholder: ''
+  }
+]);
+
+const refreshAuthChainEditable = () => {
+  if (!authChainLevels.value?.length) return;
+  authChainLevels.value.forEach((lvl, idx) => (lvl.editable = idx !== authChainLevels.value.length - 1));
+};
+
+refreshAuthChainEditable();
+
+// 第一行的分类列表
+const category1List = ref<CategoryVO[]>([]); // 一级分类
+const category2List = ref<CategoryVO[]>([]); // 二级分类
+const category3List = ref<CategoryVO[]>([]); // 三级分类
+
+// 额外添加的分类行(只有二级和三级)
+interface ExtraCategoryRow {
+  id: number;
+  category2: string | number;
+  category3: string | number;
+  category2List: CategoryVO[];
+  category3List: CategoryVO[];
+}
+
+const extraCategoryRows = ref<ExtraCategoryRow[]>([]);
+let extraCategoryRowIdCounter = 0;
+
+// 资质文件列表
+const fileList = ref([
+  { 
+    index: 1, 
+    typeName: '商标注册证', 
+    fileOssIds: '', 
+    expireDate: '',
+    fileDetails: [] as any[] // 缓存文件详情
+  },
+  { 
+    index: 2, 
+    typeName: '品牌授权书', 
+    fileOssIds: '', 
+    expireDate: '',
+    fileDetails: [] as any[]
+  },
+  { 
+    index: 3, 
+    typeName: '质检报告', 
+    fileOssIds: '', 
+    expireDate: '',
+    fileDetails: [] as any[]
+  }
+]);
+
+// 文件名称缓存
+const fileNamesCache = ref<Map<string, string[]>>(new Map());
+
+// 文件上传对话框
+const fileUploadDialogVisible = ref(false);
+const currentFileRow = ref<any>({});
+
+// 授权区域列表
+const areaList = ref<any[]>([]);
+
+// 授权区域对话框相关
+const areaDialogVisible = ref(false);
+const areaSubmitLoading = ref(false);
+const selectedAreas = ref<any[]>([]); // 选中的授权区域(级联选择器的值)
+const areaOptions = ref<any[]>([]); // 授权区域选项(从接口获取)
+const selectedAreaIds = ref<string[]>([]); // 选中的区域ID列表(用于提交)
+
+const cascaderProps = {
+  multiple: true,
+  value: 'value', // 使用 component 自带的 value(即行政区划代码如 110000)
+  label: 'label',
+  children: 'children',
+  checkStrictly: false,
+  emitPath: true
+};
+
+const toProvinceCityOptions = (options: any[]) => {
+  return (options || []).map((province) => {
+    const provinceValue6 = String(province.value ?? '');
+    const provinceValue = provinceValue6.length >= 2 ? provinceValue6.slice(0, 2) : provinceValue6;
+
+    const cities = (province.children || []).map((city: any) => {
+      const cityValue6 = String(city.value ?? '');
+      const cityValue = cityValue6.length >= 4 ? cityValue6.slice(0, 4) : cityValue6;
+      const { children, ...rest } = city;
+      return {
+        ...rest,
+        value: cityValue
+      };
+    });
+
+    return {
+      ...province,
+      value: provinceValue,
+      children: cities
+    };
+  });
+};
+
+const isCityCode = (val: any) => {
+  const s = String(val ?? '').trim();
+  return /^\d{4}$/.test(s);
+};
+
+/** 返回 */
+const goBack = () => {
+  router.back();
+};
+
+/** 加载详情数据(编辑模式) */
+const loadDetailData = async () => {
+  try {
+    console.log('=== 加载详情数据 ===');
+    console.log('编辑ID:', editId.value);
+    
+    const res = await getBrandAuthorizeDetail(editId.value);
+    const data = res.data;
+    
+    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 || '';
+    formData.value.authorizeTypeId = Number(data.authorizeType) || '';
+    formData.value.authorizeLevel = data.authorizeLevel || '';
+    
+    // 根据品牌名称找到对应的品牌ID(需要等待品牌列表加载完成)
+    if (data.brandName) {
+      // 如果品牌列表还没加载完成,等待一下
+      let retryCount = 0;
+      while (brandList.value.length === 0 && retryCount < 10) {
+        await new Promise(resolve => setTimeout(resolve, 100));
+        retryCount++;
+      }
+      
+      if (brandList.value.length > 0) {
+        const matchedBrand = brandList.value.find(brand => brand.brandName === data.brandName);
+        if (matchedBrand) {
+          formData.value.brandId = matchedBrand.id;
+          console.log('根据品牌名称找到品牌ID:', { brandName: data.brandName, brandId: matchedBrand.id });
+        } else {
+          // 如果找不到匹配的品牌,设置一个临时ID避免验证失败
+          formData.value.brandId = 'edit_mode_brand_id';
+          console.warn('未找到匹配的品牌,使用临时ID');
+        }
+      } else {
+        formData.value.brandId = 'edit_mode_brand_id';
+        console.warn('品牌列表未加载,使用临时ID');
+      }
+    }
+    
+    console.log('授权类型回显:', { 
+      原始值: data.authorizeType, 
+      转换后: Number(data.authorizeType),
+      类型: typeof Number(data.authorizeType)
+    });
+    
+    // 回显分类信息(根据 categoryId 和 categorysMap)
+    if (data.categoryId && data.categorysMap) {
+      // 设置三级分类ID(这是实际需要提交的)
+      formData.value.category3 = data.categoryId;
+      
+      // 为了通过验证,也设置一级和二级分类的临时值
+      formData.value.category1 = 'edit_mode_category1';
+      formData.value.category2 = 'edit_mode_category2';
+      
+      // 存储分类名称用于显示
+      categoryDisplayNames.value = {
+        oneLevelName: data.categorysMap.oneLevelName || '',
+        twoLevelName: data.categorysMap.twoLevelName || '',
+        threeLevelName: data.categorysMap.threeLevelName || ''
+      };
+      
+      console.log('分类信息回显:', {
+        categoryId: data.categoryId,
+        names: categoryDisplayNames.value,
+        formData: {
+          category1: formData.value.category1,
+          category2: formData.value.category2,
+          category3: formData.value.category3
+        }
+      });
+    }
+    
+    // 回显授权链路
+    if (data.brandLicensor) {
+      const licensors = data.brandLicensor.split(',');
+      authChainLevels.value = [];
+      
+      licensors.forEach((licensor, index) => {
+        authChainLevels.value.push({
+          id: authLevelIdCounter++,
+          label: `${index}级授权`,
+          value: licensor.trim(),
+          editable: index !== licensors.length - 1,
+          placeholder: '请输入授权人'
+        });
+      });
+      console.log('授权链路回显:', authChainLevels.value);
+    }
+    
+    // 回显授权区域
+    if (data.authorizedArea) {
+      const areaIds = data.authorizedArea.split(',');
+      selectedAreaIds.value = areaIds;
+      
+      // 为级联选择器设置选中值
+      // 需要根据区域ID在 areaOptions 中找到对应的省市关系
+      selectedAreas.value = await buildCascaderValues(areaIds);
+      
+      areaList.value = buildAreaRows(selectedAreas.value);
+      console.log('授权区域回显:', { 
+        areaIds, 
+        selectedAreas: selectedAreas.value,
+        rows: areaList.value
+      });
+    }
+    
+    // 回显资质文件
+    if (data.qualificationFiles && data.qualificationFiles.length > 0) {
+      // 按文件类型分组
+      const filesByType = {
+        '商标注册证': [],
+        '品牌授权书': [],
+        '质检报告': []
+      };
+      
+      data.qualificationFiles.forEach(file => {
+        if (filesByType[file.name]) {
+          filesByType[file.name].push(file);
+        }
+      });
+      
+      // 回显到对应的文件列表
+      fileList.value.forEach(fileRow => {
+        const files = filesByType[fileRow.typeName];
+        if (files && files.length > 0) {
+          // 构造 OSS ID 字符串(模拟上传组件的格式)
+          const ossIds = files.map(f => f.id).join(',');
+          fileRow.fileOssIds = ossIds;
+          
+          // 缓存文件详情
+          fileRow.fileDetails = files.map(f => ({
+            id: f.id,
+            originalName: f.fileName,
+            url: f.fileUrl,
+            fileSuffix: f.fileType
+          }));
+          
+          // 设置到期日期(使用第一个文件的到期日期)
+          if (files[0].endTime) {
+            fileRow.expireDate = files[0].endTime.split(' ')[0]; // 只取日期部分
+          }
+          
+          // 缓存文件名称
+          const fileNames = files.map(f => f.fileName);
+          fileNamesCache.value.set(ossIds, fileNames);
+          
+          console.log(`${fileRow.typeName} 文件回显:`, {
+            ossIds,
+            fileNames,
+            fileDetails: fileRow.fileDetails,
+            expireDate: fileRow.expireDate
+          });
+        }
+      });
+      
+      console.log('资质文件回显完成:', fileList.value);
+    }
+    
+    ElMessage.success('数据加载成功');
+  } catch (error) {
+    console.error('加载详情数据失败:', error);
+    ElMessage.error('加载数据失败');
+  }
+};
+
+const handleBrandClear = () => {
+  formData.value.brandId = '';
+  formData.value.brandNo = '';
+  formData.value.brandName = '';
+  formData.value.brandLogo = '';
+  formData.value.brandRegistrant = '';
+};
+
+
+
+/** 根据三级分类ID加载分类信息 */
+const loadCategoryByThirdLevel = async (categoryId: string | number) => {
+  // 这里需要根据三级分类ID反向查找一级、二级分类
+  // 暂时留空,后续需要完善
+  console.log('需要根据三级分类ID加载分类信息:', categoryId);
+};
+
+/** 获取品牌列表 */
+const getBrandList = async () => {
+  try {
+    const res = await listBrand({
+      pageNum: 1,
+      pageSize: 1000
+    });
+    brandList.value = res.rows || [];
+    console.log('品牌列表:', brandList.value);
+  } catch (e) {
+    console.error('获取品牌列表失败:', e);
+  }
+};
+
+/** 获取授权类型列表 */
+const getAuthorizeTypeList = async () => {
+  try {
+    const res = await listAuthorizetypeLevel({
+      pageNum: 1,
+      pageSize: 100
+    });
+    authorizeTypeList.value = res.rows || [];
+    console.log('授权类型列表:', authorizeTypeList.value);
+  } catch (e) {
+    console.error('获取授权类型列表失败:', e);
+  }
+};
+
+/** 授权类型改变 */
+const handleAuthorizeTypeChange = (id: string | number) => {
+  const selected = authorizeTypeList.value.find(item => item.id === id);
+  if (selected) {
+    formData.value.authorizeLevel = String(selected.authorizeLevel);
+    console.log('选择授权类型:', { 
+      id: id, 
+      authorizeType: selected.authorizeType,
+      authorizeLevel: selected.authorizeLevel 
+    });
+  }
+};
+
+/** 品牌选择改变 */
+const handleBrandChange = (brandName: string) => {
+  if (!brandName) {
+    formData.value.brandId = '';
+    formData.value.brandNo = '';
+    formData.value.brandName = '';
+    formData.value.brandLogo = '';
+    formData.value.brandRegistrant = '';
+    return;
+  }
+  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
+    });
+  }
+};
+
+/** 获取一级分类列表 */
+const getCategory1List = async () => {
+  try {
+    const res = await listCategory({
+      classLevel: 1,
+      pageNum: 1,
+      pageSize: 1000
+    });
+    category1List.value = res.rows || [];
+    console.log('一级分类:', category1List.value);
+  } catch (e) {
+    console.error('获取一级分类失败:', e);
+  }
+};
+
+/** 第一行:一级分类改变 */
+const handleCategory1Change = async (value: string | number) => {
+  // 清空二级和三级分类
+  formData.value.category2 = '';
+  formData.value.category3 = '';
+  category2List.value = [];
+  category3List.value = [];
+  
+  // 清空所有额外的分类行
+  extraCategoryRows.value = [];
+  
+  if (!value) return;
+  
+  // 根据一级分类ID查询二级分类
+  try {
+    const res = await listCategory({
+      parentId: value,
+      classLevel: 2,
+      pageNum: 1,
+      pageSize: 1000
+    });
+    category2List.value = res.rows || [];
+    console.log('二级分类:', category2List.value);
+  } catch (e) {
+    console.error('获取二级分类失败:', e);
+  }
+};
+
+/** 获取所有已选中的三级分类ID */
+const getSelectedCategory3Ids = (): (string | number)[] => {
+  const ids: (string | number)[] = [];
+  
+  // 第一行的三级分类
+  if (formData.value.category3) {
+    ids.push(formData.value.category3);
+  }
+  
+  // 额外行的三级分类
+  extraCategoryRows.value.forEach(row => {
+    if (row.category3) {
+      ids.push(row.category3);
+    }
+  });
+  
+  return ids;
+};
+
+/** 第一行:二级分类改变 */
+const handleCategory2Change = async (value: string | number) => {
+  // 清空三级分类
+  formData.value.category3 = '';
+  category3List.value = [];
+  
+  if (!value) return;
+  
+  // 根据二级分类ID查询三级分类
+  try {
+    const res = await listCategory({
+      parentId: value,
+      classLevel: 3,
+      pageNum: 1,
+      pageSize: 1000
+    });
+    
+    // 过滤掉已选中的三级分类(排除当前行自己的选择)
+    const selectedIds = extraCategoryRows.value
+      .map(row => row.category3)
+      .filter(id => id);
+    
+    category3List.value = (res.rows || []).filter(
+      (item: CategoryVO) => !selectedIds.includes(item.id)
+    );
+    
+    console.log('三级分类:', category3List.value);
+  } catch (e) {
+    console.error('获取三级分类失败:', e);
+  }
+};
+
+/** 第一行:三级分类改变 */
+const handleCategory3Change = (value: string | number) => {
+  if (!value) return;
+  
+  // 检查是否已经在额外行中选择了相同的三级分类
+  const isDuplicate = extraCategoryRows.value.some(row => row.category3 === value);
+  
+  if (isDuplicate) {
+    ElMessage.warning('该分类已经选择过了,请选择其他分类');
+    formData.value.category3 = '';
+  }
+};
+
+/** 额外行:二级分类改变 */
+const handleExtraCategory2Change = async (row: ExtraCategoryRow, value: string | number) => {
+  // 清空三级分类
+  row.category3 = '';
+  row.category3List = [];
+  
+  if (!value) return;
+  
+  // 根据二级分类ID查询三级分类
+  try {
+    const res = await listCategory({
+      parentId: value,
+      classLevel: 3,
+      pageNum: 1,
+      pageSize: 1000
+    });
+    
+    // 过滤掉已选中的三级分类(排除当前行自己的选择)
+    const selectedIds = getSelectedCategory3Ids().filter(id => id !== row.category3);
+    
+    row.category3List = (res.rows || []).filter(
+      (item: CategoryVO) => !selectedIds.includes(item.id)
+    );
+    
+    console.log('额外行三级分类:', row.category3List);
+  } catch (e) {
+    console.error('获取三级分类失败:', e);
+  }
+};
+
+/** 额外行:三级分类改变 */
+const handleExtraCategory3Change = (row: ExtraCategoryRow, value: string | number) => {
+  if (!value) return;
+  
+  // 检查是否与第一行的三级分类重复
+  if (formData.value.category3 === value) {
+    ElMessage.warning('该分类已经选择过了,请选择其他分类');
+    row.category3 = '';
+    return;
+  }
+  
+  // 检查是否与其他额外行的三级分类重复
+  const isDuplicate = extraCategoryRows.value.some(r => r.id !== row.id && r.category3 === value);
+  
+  if (isDuplicate) {
+    ElMessage.warning('该分类已经选择过了,请选择其他分类');
+    row.category3 = '';
+  }
+};
+
+/** 添加分类行(只添加二级和三级) */
+const handleAddCategory = async () => {
+  // 必须先选择一级分类
+  if (!formData.value.category1) {
+    ElMessage.warning('请先选择一级分类');
+    return;
+  }
+  
+  // 使用第一行的二级分类列表
+  extraCategoryRows.value.push({
+    id: ++extraCategoryRowIdCounter,
+    category2: '',
+    category3: '',
+    category2List: [...category2List.value], // 复用第一行的二级分类列表
+    category3List: []
+  });
+  
+  console.log('添加额外分类行,当前行数:', extraCategoryRows.value.length);
+};
+
+/** 删除额外分类行 */
+const handleDeleteCategory = (row: ExtraCategoryRow) => {
+  const index = extraCategoryRows.value.findIndex(r => r.id === row.id);
+  if (index > -1) {
+    extraCategoryRows.value.splice(index, 1);
+    ElMessage.success('删除成功');
+  }
+};
+
+/** 添加授权人 */
+const handleAddPerson = () => {
+  if (!formData.value.authPerson) {
+    ElMessage.warning('请输入授权人');
+    return;
+  }
+  ElMessage.success('授权人添加成功');
+};
+
+/** 删除授权人 */
+const handleDeletePerson = () => {
+  formData.value.authPerson = '';
+  ElMessage.success('删除成功');
+};
+
+/** 添加授权级别 */
+const handleAddAuthLevel = () => {
+  if (authChainLevels.value.length >= 5) {
+    ElMessage.warning('授权链路最多支持5个级别');
+    return;
+  }
+  // 在最前面插入新的0级授权
+  // 所有现有级别的标签都要+1
+  const newLevels: AuthChainLevel[] = [
+    {
+      id: authLevelIdCounter++,
+      label: '0级授权',
+      value: '',
+      editable: true,
+      placeholder: '请输入授权人'
+    }
+  ];
+  
+  // 将现有的级别标签都+1
+  authChainLevels.value.forEach((level, index) => {
+    newLevels.push({
+      id: level.id, // 保持唯一身份标识避免页面组件渲染错误
+      label: `${index + 1}级授权`,
+      value: level.value,
+      editable: true,
+      placeholder: level.placeholder
+    });
+  });
+
+  // 仅最后一级不可编辑
+  if (newLevels.length) {
+    newLevels.forEach((lvl, idx) => (lvl.editable = idx !== newLevels.length - 1));
+  }
+  authChainLevels.value = newLevels;
+  ElMessage.success('添加成功');
+};
+
+/** 删除授权级别 */
+const handleDeleteAuthLevel = () => {
+  if (authChainLevels.value.length <= 2) {
+    ElMessage.warning('至少保留两级授权');
+    return;
+  }
+  
+  // 删除第一个(0级授权)
+  authChainLevels.value.shift();
+  
+  // 重新调整所有级别的标签
+  authChainLevels.value = authChainLevels.value.map((level, index) => ({
+    ...level,
+    label: `${index}级授权`,
+    editable: true
+  }));
+
+  // 仅最后一级不可编辑
+  if (authChainLevels.value.length) {
+    authChainLevels.value.forEach((lvl, idx) => (lvl.editable = idx !== authChainLevels.value.length - 1));
+  }
+  
+  ElMessage.success('删除成功');
+};
+
+/** 获取文件名称列表 */
+const getFileNames = (ossIds: string): string[] => {
+  if (!ossIds) return [];
+  
+  // 检查缓存
+  if (fileNamesCache.value.has(ossIds)) {
+    return fileNamesCache.value.get(ossIds)!;
+  }
+  
+  // 在编辑模式下,尝试从 fileDetails 中获取文件名
+  if (isEditMode.value) {
+    const fileRow = fileList.value.find(row => row.fileOssIds === ossIds);
+    if (fileRow && fileRow.fileDetails && fileRow.fileDetails.length > 0) {
+      const fileNames = fileRow.fileDetails.map((detail: any) => detail.originalName);
+      fileNamesCache.value.set(ossIds, fileNames);
+      return fileNames;
+    }
+  }
+  
+  // 异步加载文件名称(新增模式或缓存未命中时)
+  loadFileNames(ossIds);
+  
+  return [];
+};
+
+/** 异步加载文件名称 */
+const loadFileNames = async (ossIds: string) => {
+  try {
+    const res = await listByIds(ossIds);
+    const fileNames = res.data.map((oss: any) => oss.originalName);
+    fileNamesCache.value.set(ossIds, fileNames);
+  } catch (e) {
+    console.error('获取文件名称失败:', e);
+  }
+};
+
+// 监听文件变化,更新文件名称
+watch(
+  () => fileList.value.map(f => f.fileOssIds),
+  (newValues, oldValues) => {
+    newValues.forEach((ossIds, index) => {
+      // 只有当ossIds变化且不为空时才加载
+      if (ossIds && ossIds !== oldValues?.[index]) {
+        // 在编辑模式下,如果已经有缓存的文件详情,就不要重新请求OSS
+        if (isEditMode.value && fileList.value[index].fileDetails && fileList.value[index].fileDetails.length > 0) {
+          console.log('编辑模式:跳过OSS请求,使用已缓存的文件详情');
+          return;
+        }
+        
+        loadFileNames(ossIds);
+        // 同时加载文件详情并缓存
+        loadFileDetails(index, ossIds);
+      }
+    });
+  },
+  { deep: true }
+);
+
+/** 加载文件详情并缓存 */
+const loadFileDetails = async (fileIndex: number, ossIds: string) => {
+  try {
+    const res = await listByIds(ossIds);
+    fileList.value[fileIndex].fileDetails = res.data || [];
+    console.log(`文件详情已缓存 [${fileList.value[fileIndex].typeName}]:`, res.data);
+  } catch (e) {
+    console.error('获取文件详情失败:', e);
+  }
+};
+
+/** 上传文件 */
+const handleUploadFile = (row: any) => {
+  currentFileRow.value = row;
+  fileUploadDialogVisible.value = true;
+};
+
+/** 下载文件 */
+const handleDownloadFile = async (row: any) => {
+  if (!row.fileOssIds) {
+    ElMessage.warning('暂无文件可下载');
+    return;
+  }
+  
+  try {
+    const res = await listByIds(row.fileOssIds);
+    if (res.data && res.data.length > 0) {
+      // 下载所有文件
+      res.data.forEach((file: any) => {
+        const link = document.createElement('a');
+        link.href = file.url;
+        link.download = file.originalName;
+        link.target = '_blank';
+        document.body.appendChild(link);
+        link.click();
+        document.body.removeChild(link);
+      });
+      ElMessage.success('开始下载');
+    }
+  } catch (e) {
+    console.error('下载文件失败:', e);
+    ElMessage.error('下载失败');
+  }
+};
+
+/** 删除文件 */
+const handleDeleteFile = (row: any) => {
+  ElMessageBox.confirm('确定要删除该资质的所有文件吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(() => {
+    row.fileOssIds = '';
+    // 清除缓存
+    fileNamesCache.value.forEach((value, key) => {
+      if (key.includes(row.fileOssIds)) {
+        fileNamesCache.value.delete(key);
+      }
+    });
+    ElMessage.success('删除成功');
+  }).catch(() => {
+    // 取消删除
+  });
+};
+
+/** 根据区域ID构建级联选择器的值 */
+const buildCascaderValues = async (areaIds: string[]): Promise<any[]> => {
+  const cascaderValues: any[] = [];
+  
+  // 递归寻找属于叶子节点的路径
+  const findLeafPath = (options: any[], targetId: string, currentPath: string[]): boolean => {
+    for (const option of options) {
+      const path = [...currentPath, option.value];
+      if (option.value === targetId) {
+        // 找到了对应的ID,判断是否为叶子节点
+        // 因为 Cascader 的 checkStrictly: false, 父节点的显式勾选会导致级联其下所有节点
+        // 只有是叶子节点(即没有 children),我们才作为有效路径提供给 Cascader 回显
+        if (!option.children || option.children.length === 0) {
+          cascaderValues.push(path);
+        }
+        return true; 
+      }
+      if (option.children && option.children.length > 0) {
+        if (findLeafPath(option.children, targetId, path)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  };
+
+  // 遍历所有区域ID,仅当它代表叶子节点时才添加路径去激活回显
+  areaIds
+    .map((areaId) => String(areaId).trim())
+    .filter((strId) => strId && isCityCode(strId))
+    .forEach((cityId) => {
+      findLeafPath(areaOptions.value, cityId, []);
+    });
+  
+  console.log('构建级联选择器值:', { areaIds, cascaderValues });
+  return cascaderValues;
+};
+
+/** 选择区域 */
+const handleSelectArea = () => {
+  areaDialogVisible.value = true;
+};
+
+/** 初始化授权区域选项数据 */
+const initAreaOptions = async () => {
+  try {
+    areaOptions.value = toProvinceCityOptions(regionData as any);
+    console.log('授权区域选项数据加载完成(使用 element-china-area-data):', areaOptions.value.length);
+  } catch (e) {
+    console.error('获取授权区域数据失败:', e);
+  }
+};
+
+/** 提交授权区域 */
+const handleAreaSubmit = async () => {
+  try {
+    areaSubmitLoading.value = true;
+
+    console.log('=== 授权区域提交 ===');
+    console.log('selectedAreas.value:', selectedAreas.value);
+    
+    const areaRows = buildAreaRows(selectedAreas.value);
+    
+    // 提取所有选中的区域ID(包括省和市的ID),使用后端短编码格式(11、1101)
+    const areaIds: string[] = [];
+    selectedAreas.value.forEach(path => {
+      if (Array.isArray(path)) {
+        // path 是 [省ID, 市ID]
+        console.log('处理路径:', path);
+        path.forEach(id => {
+          console.log('提取ID:', id, '类型:', typeof id);
+          const strId = String(id).trim();
+          if (strId && !areaIds.includes(strId)) {
+            areaIds.push(strId);
+          }
+        });
+      }
+    });
+    
+    selectedAreaIds.value = areaIds;
+
+    // 更新授权区域列表显示
+    areaList.value = areaRows;
+
+    console.log('选中的授权区域名称:', areaRows);
+    console.log('选中的区域ID列表(用于提交):', selectedAreaIds.value);
+    console.log('=== 授权区域提交完成 ===');
+    ElMessage.success('授权区域设置成功');
+    areaDialogVisible.value = false;
+  } catch (e) {
+    console.error('设置授权区域失败:', e);
+    ElMessage.error('设置失败');
+  } finally {
+    areaSubmitLoading.value = false;
+  }
+};
+
+const buildAreaRows = (selectedPaths: any[]) => {
+  const provinceMap = new Map<string, Set<string>>();
+
+  const normalizeCityDisplayName = (provinceName: string, cityName: string) => {
+    const p = String(provinceName || '');
+    const c = String(cityName || '');
+    if (!c) return '';
+    if (c === '市辖区') {
+      return p.endsWith('市') ? p : `${p}市`;
+    }
+    return c;
+  };
+
+  selectedPaths.forEach((path) => {
+    if (Array.isArray(path) && path.length >= 2) {
+      const [provinceId, cityId] = path;
+      const provinceName = findRegionNameById(provinceId, areaOptions.value);
+      const province = areaOptions.value.find((p) => p.value === String(provinceId));
+      const cityName = province && cityId ? findRegionNameById(cityId, province.children || []) : '';
+
+      if (!provinceName) return;
+      if (!provinceMap.has(provinceName)) {
+        provinceMap.set(provinceName, new Set());
+      }
+      const displayCityName = normalizeCityDisplayName(provinceName, cityName);
+      if (displayCityName) {
+        provinceMap.get(provinceName)!.add(displayCityName);
+      }
+    }
+  });
+
+  return Array.from(provinceMap.entries()).map(([province, cities]) => {
+    return {
+      province,
+      city: Array.from(cities).join(',')
+    };
+  });
+};
+
+/** 从级联选择器的值中提取区域数据 */
+const extractRegionData = (selectedPaths: any[]) => {
+  // 用于存储省-市的映射关系
+  const provinceMap = new Map<string, Set<string>>();
+
+  selectedPaths.forEach(path => {
+    if (Array.isArray(path) && path.length >= 2) {
+      const [provinceId, cityId] = path;
+      
+      // 查找对应的名称(通过value查找)
+      const provinceName = findRegionNameById(provinceId, areaOptions.value);
+      const province = areaOptions.value.find(p => p.value === provinceId);
+      const cityName = province && cityId ? findRegionNameById(cityId, province.children || []) : '';
+
+      if (provinceName) {
+        if (!provinceMap.has(provinceName)) {
+          provinceMap.set(provinceName, new Set());
+        }
+        
+        // 直辖市的处理(市辖区转为原来的名称)
+        if (cityName && cityName !== '市辖区') {
+          provinceMap.get(provinceName)!.add(cityName);
+        } else if (cityName === '市辖区') {
+          provinceMap.get(provinceName)!.add(provinceName);
+        }
+      }
+    }
+  });
+
+  // 生成省份和城市字符串
+  const provinces: string[] = [];
+  const cities: string[] = [];
+
+  provinceMap.forEach((citySet, provinceName) => {
+    provinces.push(provinceName);
+    cities.push(Array.from(citySet).join(','));
+  });
+
+  return {
+    provinces: provinces.join(','),
+    cities: cities.join(';')
+  };
+};
+
+/** 根据value查找区域名称 */
+const findRegionNameById = (id: string | number, regions: any[]): string => {
+  const targetId = String(id);
+  const region = regions.find(r => r.value === targetId);
+  return region ? region.label : '';
+};
+
+/** 提交 */
+const handleSubmit = async () => {
+  try {
+    // 验证必填项
+    if (!formData.value.brandId || !formData.value.brandName) {
+      ElMessage.warning('请选择品牌');
+      return;
+    }
+    
+    // 编辑模式下跳过分类验证,因为分类是锁死的
+    if (!isEditMode.value) {
+      if (!formData.value.category1 || !formData.value.category2 || !formData.value.category3) {
+        ElMessage.warning('请选择完整的分类');
+        return;
+      }
+    }
+    
+    if (!userStore.supplierId) {
+      ElMessage.error('供应商ID不存在');
+      return;
+    }
+
+    // 收集所有分类数据
+    const allCategories = [
+      {
+        category1: formData.value.category1,
+        category2: formData.value.category2,
+        category3: formData.value.category3
+      },
+      ...extraCategoryRows.value.map(row => ({
+        category1: formData.value.category1,
+        category2: row.category2,
+        category3: row.category3
+      }))
+    ];
+    
+    // 提取所有三级分类ID(只传三级分类ID的列表,并去重)
+    const categoryIds = [...new Set(
+      allCategories
+        .map(cat => cat.category3)
+        .filter(id => id) // 过滤掉空值
+    )];
+    
+    // 验证是否有重复的分类
+    if (categoryIds.length !== allCategories.filter(cat => cat.category3).length) {
+      console.warn('检测到重复的分类选择,已自动去重');
+    }
+    
+    // 构建授权区域字符串(使用ID,用逗号分隔)
+    const authorizedArea = selectedAreaIds.value.join(',');
+    
+    console.log('=== 构建授权区域 ===');
+    console.log('selectedAreaIds.value:', selectedAreaIds.value);
+    console.log('authorizedArea (提交给后端):', authorizedArea);
+    console.log('类型检查 - 是否包含数字:', selectedAreaIds.value.map(id => ({ id, type: typeof id })));
+    
+    // 构建授权链路字符串(从0级到最高级,用逗号分隔)
+    const brandLicensor = authChainLevels.value
+      .map(level => level.value)
+      .filter(value => value) // 过滤掉空值
+      .join(',');
+    
+    // 构建资质文件集合(使用缓存的文件详情)
+    const qualificationFiles: QualificationFileForm[] = [];
+    
+    for (const file of fileList.value) {
+      if (file.fileOssIds && file.fileDetails.length > 0) {
+        // 使用缓存的文件详情,无需再次请求
+        file.fileDetails.forEach((ossFile: any) => {
+          qualificationFiles.push({
+            name: file.typeName, // 资质类型名称(商标注册证、品牌授权书、质检报告)
+            fileName: ossFile.originalName, // 文件名称
+            fileType: ossFile.fileSuffix || '', // 文件类型
+            fileUrl: ossFile.url, // 文件URL
+            endTime: file.expireDate ? new Date(file.expireDate).toISOString() : '' // 资质到期日期
+          });
+        });
+      }
+    }
+    
+    console.log('使用缓存的文件详情,无需等待OSS请求');
+    
+    // 构建提交数据
+    const submitData: any = {
+      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,
+      authorizeType: formData.value.authorizeTypeId, // 授权类型ID
+      authorizeLevel: formData.value.authorizeLevel, // 授权等级
+      categoryIds: categoryIds, // 三级分类ID列表
+      authorizedArea: authorizedArea, // 授权地址(区域ID列表)
+      brandLicensor: brandLicensor, // 授权链路(授权人列表)
+      qualificationFiles: qualificationFiles // 资质文件集合
+    };
+    
+    console.log('提交数据:', submitData);
+    console.log('三级分类ID列表:', categoryIds);
+    console.log('授权地址:', authorizedArea);
+    console.log('授权链路:', brandLicensor);
+    console.log('资质文件集合:', qualificationFiles);
+    
+    // 调用API(根据模式调用不同的接口)
+    if (isEditMode.value) {
+      // 编辑模式:调用更新接口
+      await updateSupplierauthorize({ ...submitData, id: editId.value });
+      ElMessage.success('更新成功');
+    } else {
+      // 新增模式:调用新增接口
+      await addSupplierauthorize(submitData);
+      ElMessage.success('提交成功');
+    }
+    
+    router.back();
+  } catch (error) {
+    console.error('提交失败:', error);
+    ElMessage.error('提交失败,请重试');
+  }
+};
+
+onMounted(async () => {
+  console.log('=== Edit页面初始化 ===');
+  console.log('route.query:', route.query);
+  console.log('route.query.id:', route.query.id);
+  
+  // 检查是否为编辑模式
+  if (route.query.id) {
+    editId.value = route.query.id as string;
+    isEditMode.value = true;
+    console.log('✓ 编辑模式,ID:', editId.value);
+    console.log('✓ isEditMode.value:', isEditMode.value);
+    
+    // 编辑模式:并行加载基础数据和详情数据
+    await Promise.all([
+      getBrandList(),
+      getAuthorizeTypeList(),
+      getCategory1List(),
+      initAreaOptions(),
+      loadDetailData() // 详情数据也并行加载
+    ]);
+  } else {
+    console.log('✓ 新增模式');
+    console.log('✓ isEditMode.value:', isEditMode.value);
+    
+    // 新增模式:只加载基础数据
+    await Promise.all([
+      getBrandList(),
+      getAuthorizeTypeList(),
+      getCategory1List(),
+      initAreaOptions()
+    ]);
+  }
+  
+  console.log('=== Edit页面初始化完成 ===');
+});
+</script>
+
+<style scoped>
+.app-container {
+  background: #f0f2f5;
+  min-height: 100vh;
+}
+
+.page-header {
+  background: #fff;
+  padding: 16px 24px;
+  display: flex;
+  align-items: center;
+  border-bottom: 1px solid #e8e8e8;
+}
+
+.back-icon {
+  font-size: 18px;
+  cursor: pointer;
+  margin-right: 12px;
+  color: #666;
+}
+
+.back-icon:hover {
+  color: #409eff;
+}
+
+.page-title {
+  font-size: 16px;
+  font-weight: 500;
+  color: #333;
+}
+
+.form-section {
+  background: #fff;
+  margin: 20px;
+  padding: 24px;
+  border-radius: 4px;
+}
+
+.section-title {
+  font-size: 16px;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 24px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #e8e8e8;
+}
+
+.auth-form {
+  margin-bottom: 30px;
+}
+
+.extra-category-row {
+  margin-bottom: 16px;
+}
+
+.file-section,
+.area-section {
+  margin-top: 30px;
+}
+
+.file-title,
+.area-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 16px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.empty-tip {
+  text-align: center;
+  padding: 40px;
+  color: #999;
+}
+
+.submit-section {
+  margin-top: 30px;
+  text-align: center;
+}
+
+.file-name-list {
+  text-align: left;
+  padding: 8px 0;
+}
+
+.file-name-item {
+  padding: 4px 0;
+  color: #409eff;
+  font-size: 14px;
+  line-height: 1.5;
+}
+
+.file-actions {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 8px;
+}
+
+.auth-chain-container {
+  width: 100%;
+}
+
+.auth-chain-actions {
+  display: flex;
+  gap: 12px;
+}
+
+.auth-chain-custom-table {
+  width: 100%;
+  margin-bottom: 16px;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  overflow-x: auto;
+}
+
+.auth-chain-header {
+  display: flex;
+  background-color: #f5f7fa;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.auth-chain-row {
+  display: flex;
+}
+
+.auth-chain-cell {
+  flex: 1;
+  min-width: 200px;
+  padding: 12px;
+  text-align: center;
+  border-right: 1px solid #ebeef5;
+  box-sizing: border-box;
+}
+
+.auth-chain-cell:last-child {
+  border-right: none;
+}
+
+.header-cell {
+  font-weight: bold;
+  color: #909399;
+  font-size: 14px;
+}
+
+/* 只读输入框样式 - 更美观的设计 */
+.readonly-input :deep(.el-input__wrapper) {
+  background: var(--el-input-bg-color) !important;
+  border: 1px solid var(--el-border-color) !important;
+  box-shadow: none !important;
+  cursor: default !important;
+}
+
+.readonly-input :deep(.el-input__inner) {
+  background: transparent !important;
+  color: var(--el-text-color-regular) !important;
+  font-weight: 400 !important;
+
+  /* 文字居中显示 */
+  text-align: left !important;
+}
+
+.readonly-input :deep(.el-input__wrapper):hover {
+  border-color: var(--el-border-color) !important;
+  box-shadow: none !important;
+}
+
+.readonly-input :deep(.el-input__wrapper):focus-within {
+  border-color: var(--el-border-color) !important;
+  box-shadow: none !important;
+}
+
+.auth-form :deep(.el-input.is-disabled .el-input__wrapper),
+.auth-form :deep(.el-select .el-input.is-disabled .el-input__wrapper) {
+  cursor: default;
+}
+
+.readonly-not-allowed :deep(.el-input__wrapper) {
+  cursor: not-allowed !important;
+  pointer-events: auto;
+}
+
+.readonly-not-allowed :deep(.el-input__inner) {
+  cursor: default !important;
+}
+
+/* 分类区域样式 */
+.category-section {
+  margin-bottom: 20px;
+  padding: 16px;
+  background: #fafbfc;
+  border-radius: 8px;
+  border: 1px solid #e1e4e8;
+}
+
+.section-label {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 16px;
+  font-weight: 600;
+  color: #24292e;
+}
+</style>

+ 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>

+ 143 - 0
src/views/supplier/info/components/SupplyInfoTab.vue

@@ -0,0 +1,143 @@
+<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="Document" @click="emit('saveCategories')">保存</el-button>
+      </div>
+
+      <el-checkbox-group v-model="selectedCategoriesProxy" class="category-group">
+        <el-checkbox v-for="category in productCategoryList" :key="category.id" :label="String(category.id)">
+          {{ category.categoryName }}
+        </el-checkbox>
+      </el-checkbox-group>
+    </div>
+
+    <div class="info-section">
+      <div class="section-title-row">
+        <div class="section-title-left">
+          <span class="section-title-text">供货品牌</span>
+        </div>
+        <div>
+          <el-button v-if="!isViewMode" type="primary" icon="Edit" @click="emit('addBrand')">编辑</el-button>
+        </div>
+      </div>
+      <div class="brand-display-wrapper">
+        <el-tag v-for="brand in selectedBrands" :key="brand.id" :closable="!isViewMode" @close="emit('removeBrand', brand)" style="margin-right: 10px; margin-bottom: 10px;">
+          {{ brand.brandName }}
+        </el-tag>
+        <span v-if="selectedBrands.length === 0" style="color: #999;">暂无品牌信息</span>
+      </div>
+    </div>
+
+    <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="Edit" @click="emit('editSupplyArea')">编辑</el-button>
+      </div>
+      <el-table :data="supplyAreaList" border style="width: 100%">
+        <el-table-column type="index" label="序号" align="center" width="80" />
+        <el-table-column prop="province" label="供货区域(省)" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.province || '' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="city" label="供货区域(市)" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.city || '' }}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <div class="info-section">
+      <div class="section-title">授权详情信息列表</div>
+      <el-table :data="authorizationList" border style="width: 100%">
+        <el-table-column type="index" label="序号" align="center" width="80" />
+        <el-table-column prop="supplierName" label="供应商名称" align="center" />
+        <el-table-column prop="brandName" label="品牌名称" align="center" />
+        <el-table-column label="一级类目" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.categorysMap?.oneLevelName || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="二级类目" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.categorysMap?.twoLevelName || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="三级类目" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.categorysMap?.threeLevelName || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="authorizationEndTime" label="禁止时间" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.authorizationEndTime ? formatDate(scope.row.authorizationEndTime) : '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="province" label="授权区域(省)" align="center" show-overflow-tooltip />
+        <el-table-column prop="city" label="授权区域(市)" align="center" show-overflow-tooltip />
+        <el-table-column prop="authorizedStatus" label="状态" align="center">
+          <template #default="scope">
+            <span>{{ getAuthorizedStatusText(scope.row.authorizedStatus) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="100">
+          <template #default="scope">
+            <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div style="margin-top: 20px; display: flex; justify-content: flex-end;">
+        <Pagination v-model:page="authorizationPagination.pageNum" v-model:limit="authorizationPagination.pageSize" :total="authorizationPagination.total" @pagination="emit('authorizationPagination')" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue';
+import { useRouter } from 'vue-router';
+
+const router = useRouter();
+
+const props = defineProps<{
+  isViewMode: boolean;
+  productCategoryList: any[];
+  selectedCategories: string[];
+  selectedBrands: any[];
+  supplyAreaList: any[];
+  authorizationList: any[];
+  authorizationPagination: { pageNum: number; pageSize: number; total: number };
+  formatDate: (date: any) => string;
+  getAuthorizedStatusText: (status: string) => string;
+}>();
+
+const emit = defineEmits<{
+  (e: 'saveCategories'): void;
+  (e: 'addBrand'): void;
+  (e: 'removeBrand', brand: any): void;
+  (e: 'editSupplyArea'): void;
+  (e: 'authorizationPagination'): void;
+  (e: 'update:selectedCategories', value: string[]): void;
+}>();
+
+const selectedCategoriesProxy = computed({
+  get: () => props.selectedCategories,
+  set: (value) => emit('update:selectedCategories', value)
+});
+
+/** 查看授权详情,跳转到 detail 页面 */
+const handleView = (row: any) => {
+  router.push({
+    path: '/supplier/author/detail',
+    query: { id: row.id }
+  });
+};
+</script>

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 698 - 218
src/views/supplier/info/index.vue


Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно