edit.vue 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749
  1. <template>
  2. <div class="app-container">
  3. <!-- 页面头部 -->
  4. <div class="page-header">
  5. <el-icon class="back-icon" @click="goBack"><ArrowLeft /></el-icon>
  6. <span class="page-title">{{ isEditMode ? '修改产品线授权' : '新增产品线授权' }}</span>
  7. </div>
  8. <!-- 设置授权 -->
  9. <div class="form-section">
  10. <div class="section-title">设置授权</div>
  11. <el-form :model="formData" label-width="100px" class="auth-form">
  12. <el-row :gutter="20">
  13. <el-col :span="8">
  14. <el-form-item label="品牌名称:">
  15. <el-select
  16. v-if="!isEditMode"
  17. v-model="formData.brandName"
  18. placeholder="请选择"
  19. clearable
  20. filterable
  21. style="width: 100%;"
  22. @change="handleBrandChange"
  23. @clear="handleBrandClear"
  24. >
  25. <el-option
  26. v-for="brand in brandList"
  27. :key="brand.id"
  28. :label="brand.brandName"
  29. :value="brand.brandName"
  30. />
  31. </el-select>
  32. <el-input
  33. v-else
  34. v-model="formData.brandName"
  35. readonly
  36. style="width: 100%;"
  37. placeholder="品牌名称"
  38. class="readonly-input"
  39. />
  40. </el-form-item>
  41. </el-col>
  42. <el-col :span="8">
  43. <el-form-item label="商标注册人:">
  44. <el-input v-model="formData.brandRegistrant" readonly style="width: 100%;" placeholder="商标注册人" class="readonly-input readonly-not-allowed" />
  45. </el-form-item>
  46. </el-col>
  47. <el-col :span="8">
  48. <el-form-item label="授权类型:">
  49. <el-select
  50. v-model="formData.authorizeTypeId"
  51. placeholder="请选择"
  52. clearable
  53. style="width: 100%;"
  54. @change="handleAuthorizeTypeChange"
  55. >
  56. <el-option
  57. v-for="item in authorizeTypeList"
  58. :key="item.id"
  59. :label="item.authorizeType"
  60. :value="item.id"
  61. />
  62. </el-select>
  63. </el-form-item>
  64. </el-col>
  65. </el-row>
  66. <!-- 第一行:三个分类下拉框 -->
  67. <el-row :gutter="20">
  68. <el-col :span="6">
  69. <el-form-item label="选择分类:">
  70. <el-select
  71. v-if="!isEditMode"
  72. v-model="formData.category1"
  73. placeholder="请选择"
  74. clearable
  75. style="width: 100%;"
  76. @change="handleCategory1Change"
  77. >
  78. <el-option
  79. v-for="item in category1List"
  80. :key="item.id"
  81. :label="item.categoryName"
  82. :value="item.id"
  83. />
  84. </el-select>
  85. <el-input
  86. v-else
  87. :value="categoryDisplayNames.oneLevelName"
  88. readonly
  89. style="width: 100%;"
  90. placeholder="一级分类"
  91. class="readonly-input"
  92. />
  93. </el-form-item>
  94. </el-col>
  95. <el-col :span="6">
  96. <el-form-item label=" ">
  97. <el-select
  98. v-if="!isEditMode"
  99. v-model="formData.category2"
  100. placeholder="请选择"
  101. clearable
  102. style="width: 100%;"
  103. :disabled="!formData.category1"
  104. @change="handleCategory2Change"
  105. >
  106. <el-option
  107. v-for="item in category2List"
  108. :key="item.id"
  109. :label="item.categoryName"
  110. :value="item.id"
  111. />
  112. </el-select>
  113. <el-input
  114. v-else
  115. :value="categoryDisplayNames.twoLevelName"
  116. readonly
  117. style="width: 100%;"
  118. placeholder="二级分类"
  119. class="readonly-input"
  120. />
  121. </el-form-item>
  122. </el-col>
  123. <el-col :span="6">
  124. <el-form-item label=" ">
  125. <el-select
  126. v-if="!isEditMode"
  127. v-model="formData.category3"
  128. placeholder="请选择"
  129. clearable
  130. style="width: 100%;"
  131. :disabled="!formData.category2"
  132. @change="handleCategory3Change"
  133. >
  134. <el-option
  135. v-for="item in category3List"
  136. :key="item.id"
  137. :label="item.categoryName"
  138. :value="item.id"
  139. />
  140. </el-select>
  141. <el-input
  142. v-else
  143. :value="categoryDisplayNames.threeLevelName"
  144. readonly
  145. style="width: 100%;"
  146. placeholder="三级分类"
  147. class="readonly-input"
  148. />
  149. </el-form-item>
  150. </el-col>
  151. <el-col :span="6">
  152. <el-button v-if="!isEditMode" type="primary" icon="Plus" @click="handleAddCategory">添加分类</el-button>
  153. </el-col>
  154. </el-row>
  155. <!-- 额外添加的分类行(只有二级和三级) -->
  156. <div v-for="row in extraCategoryRows" :key="row.id" class="extra-category-row">
  157. <el-row :gutter="20">
  158. <el-col :span="6">
  159. <el-form-item label=" ">
  160. <!-- 占位,保持对齐 -->
  161. </el-form-item>
  162. </el-col>
  163. <el-col :span="6">
  164. <el-form-item label=" ">
  165. <el-select
  166. v-model="row.category2"
  167. placeholder="请选择"
  168. clearable
  169. style="width: 100%;"
  170. @change="(val) => handleExtraCategory2Change(row, val)"
  171. >
  172. <el-option
  173. v-for="item in row.category2List"
  174. :key="item.id"
  175. :label="item.categoryName"
  176. :value="item.id"
  177. />
  178. </el-select>
  179. </el-form-item>
  180. </el-col>
  181. <el-col :span="6">
  182. <el-form-item label=" ">
  183. <el-select
  184. v-model="row.category3"
  185. placeholder="请选择"
  186. clearable
  187. style="width: 100%;"
  188. :disabled="!row.category2"
  189. @change="(val) => handleExtraCategory3Change(row, val)"
  190. >
  191. <el-option
  192. v-for="item in row.category3List"
  193. :key="item.id"
  194. :label="item.categoryName"
  195. :value="item.id"
  196. />
  197. </el-select>
  198. </el-form-item>
  199. </el-col>
  200. <el-col :span="6">
  201. <el-button type="danger" icon="Delete" @click="handleDeleteCategory(row)">删除分类</el-button>
  202. </el-col>
  203. </el-row>
  204. </div>
  205. <!-- 授权链路 -->
  206. <el-form-item label="授权链路:">
  207. <div class="auth-chain-container">
  208. <!-- 授权链路自定义表格 -->
  209. <div class="auth-chain-custom-table">
  210. <div class="auth-chain-header">
  211. <div
  212. v-for="level in authChainLevels"
  213. :key="'h-' + level.id"
  214. class="auth-chain-cell header-cell"
  215. >
  216. {{ level.label }}
  217. </div>
  218. </div>
  219. <div class="auth-chain-row">
  220. <div
  221. v-for="level in authChainLevels"
  222. :key="'c-' + level.id"
  223. class="auth-chain-cell"
  224. >
  225. <el-input
  226. v-if="level.editable"
  227. v-model="level.value"
  228. :placeholder="level.placeholder"
  229. clearable
  230. />
  231. <span v-else>{{ level.value }}</span>
  232. </div>
  233. </div>
  234. </div>
  235. <!-- 操作按钮 -->
  236. <div class="auth-chain-actions">
  237. <el-button type="primary" @click="handleAddAuthLevel" :disabled="authChainLevels.length >= 5">添加品牌授权人</el-button>
  238. <el-button type="danger" @click="handleDeleteAuthLevel" :disabled="authChainLevels.length <= 2">删除</el-button>
  239. </div>
  240. </div>
  241. </el-form-item>
  242. <!-- 原来的授权人员输入(保留或删除,根据需求) -->
  243. <!-- <el-form-item>
  244. <el-input
  245. v-model="formData.authPerson"
  246. placeholder="请输入品牌授权人"
  247. style="width: 300px;"
  248. />
  249. <el-button type="primary" icon="Plus" style="margin-left: 10px;" @click="handleAddPerson">
  250. 添加品牌授权人
  251. </el-button>
  252. <el-button icon="Delete" @click="handleDeletePerson">删除</el-button>
  253. </el-form-item> -->
  254. </el-form>
  255. <!-- 资质文件 -->
  256. <div class="file-section">
  257. <div class="file-title">资质文件</div>
  258. <el-table :data="fileList" border style="width: 100%">
  259. <el-table-column prop="index" label="序号" align="center" width="80" />
  260. <el-table-column prop="typeName" label="资质名称" align="center" width="150" />
  261. <el-table-column label="文件名称" align="center" min-width="400">
  262. <template #default="scope">
  263. <div v-if="scope.row.fileOssIds" class="file-name-list">
  264. <div v-for="(fileName, idx) in getFileNames(scope.row.fileOssIds)" :key="idx" class="file-name-item">
  265. {{ fileName }}
  266. </div>
  267. </div>
  268. <span v-else style="color: #999;">暂无文件</span>
  269. </template>
  270. </el-table-column>
  271. <el-table-column label="资质文件" align="center" width="250">
  272. <template #default="scope">
  273. <div class="file-actions">
  274. <el-button type="primary" link @click="handleUploadFile(scope.row)">上传文件</el-button>
  275. <template v-if="scope.row.fileOssIds">
  276. <el-button type="primary" link @click="handleDownloadFile(scope.row)">下载</el-button>
  277. <el-button type="danger" link @click="handleDeleteFile(scope.row)">删除</el-button>
  278. </template>
  279. </div>
  280. </template>
  281. </el-table-column>
  282. <el-table-column label="资质到期日" align="center" width="220">
  283. <template #default="scope">
  284. <el-date-picker
  285. v-model="scope.row.expireDate"
  286. type="date"
  287. placeholder="请选择到期日"
  288. style="width: 100%;"
  289. />
  290. </template>
  291. </el-table-column>
  292. </el-table>
  293. </div>
  294. <!-- 文件上传对话框 -->
  295. <el-dialog
  296. v-model="fileUploadDialogVisible"
  297. title="上传资质文件"
  298. width="600px"
  299. :close-on-click-modal="false"
  300. >
  301. <FileUpload
  302. v-model="currentFileRow.fileOssIds"
  303. :limit="5"
  304. :file-size="10"
  305. :file-type="['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx']"
  306. />
  307. <template #footer>
  308. <el-button @click="fileUploadDialogVisible = false">关闭</el-button>
  309. </template>
  310. </el-dialog>
  311. <!-- 授权区域 -->
  312. <div class="area-section">
  313. <div class="area-title">
  314. <span>授权区域:</span>
  315. <el-button type="primary" @click="handleSelectArea">选择区域</el-button>
  316. </div>
  317. <el-table :data="areaList" border style="width: 100%">
  318. <el-table-column type="index" label="序号" align="center" width="80" />
  319. <el-table-column prop="province" label="授权区域(省)" align="center" />
  320. <el-table-column prop="city" label="授权区域(市)" align="center" />
  321. </el-table>
  322. </div>
  323. <!-- 提交按钮 -->
  324. <div class="submit-section">
  325. <el-button type="primary" @click="handleSubmit">提交</el-button>
  326. </div>
  327. </div>
  328. <!-- 授权区域选择对话框 -->
  329. <el-dialog
  330. v-model="areaDialogVisible"
  331. title="选择授权区域"
  332. width="700px"
  333. :close-on-click-modal="false"
  334. >
  335. <el-cascader
  336. v-model="selectedAreas"
  337. :options="areaOptions"
  338. :props="cascaderProps"
  339. placeholder="请选择授权区域"
  340. style="width: 100%;"
  341. clearable
  342. filterable
  343. />
  344. <template #footer>
  345. <el-button @click="areaDialogVisible = false">取消</el-button>
  346. <el-button type="primary" :loading="areaSubmitLoading" @click="handleAreaSubmit">确定</el-button>
  347. </template>
  348. </el-dialog>
  349. </div>
  350. </template>
  351. <script setup lang="ts">
  352. import { ref, onMounted, watch } from 'vue';
  353. import { useRouter, useRoute } from 'vue-router';
  354. import { ArrowLeft } from '@element-plus/icons-vue';
  355. import { ElMessage, ElMessageBox } from 'element-plus';
  356. import { listBrand } from '@/api/product/brand';
  357. import { listCategory } from '@/api/product/category';
  358. import type { CategoryVO } from '@/api/product/category/types';
  359. import { listByIds } from '@/api/system/oss';
  360. import { addSupplierauthorize, getBrandAuthorizeDetail, updateSupplierauthorize } from '@/api/supplier/supplierauthorize';
  361. import type { SupplierauthorizeForm } from '@/api/supplier/supplierauthorize/types';
  362. import type { QualificationFileForm } from '@/api/supplier/qualificationFile/types';
  363. import { listAuthorizetypeLevel } from '@/api/supplier/authorizetypeLevel';
  364. import type { AuthorizetypeLevelVO } from '@/api/supplier/authorizetypeLevel/types';
  365. import { useUserStore } from '@/store/modules/user';
  366. import FileUpload from '@/components/FileUpload/index.vue';
  367. import { regionData } from 'element-china-area-data';
  368. const router = useRouter();
  369. const route = useRoute();
  370. const userStore = useUserStore();
  371. // 判断是新增还是编辑模式
  372. const editId = ref<string | number>('');
  373. const isEditMode = ref(false);
  374. // 分类显示名称(编辑模式用)
  375. const categoryDisplayNames = ref({
  376. oneLevelName: '',
  377. twoLevelName: '',
  378. threeLevelName: ''
  379. });
  380. // 表单数据
  381. const formData = ref({
  382. brandId: '',
  383. brandNo: '',
  384. brandName: '',
  385. brandLogo: '', // 品牌LOGO
  386. brandRegistrant: '', // 商标注册人
  387. authorizeTypeId: '', // 授权类型ID
  388. authorizeLevel: '', // 授权等级
  389. category1: '', // 第一行的一级分类
  390. category2: '', // 第一行的二级分类
  391. category3: '', // 第一行的三级分类
  392. authPerson: ''
  393. });
  394. // 品牌列表
  395. const brandList = ref<any[]>([]);
  396. // 授权类型列表
  397. const authorizeTypeList = ref<AuthorizetypeLevelVO[]>([]);
  398. // 授权链路
  399. interface AuthChainLevel {
  400. id: number; // 唯一标识
  401. label: string; // 列标题(0级授权、1级授权...)
  402. value: string; // 授权人名称
  403. editable: boolean; // 是否可编辑
  404. placeholder: string; // 输入框提示
  405. }
  406. let authLevelIdCounter = 0;
  407. const authChainLevels = ref<AuthChainLevel[]>([
  408. {
  409. id: authLevelIdCounter++,
  410. label: '0级授权',
  411. value: '',
  412. editable: true,
  413. placeholder: '请输入授权人'
  414. },
  415. {
  416. id: authLevelIdCounter++,
  417. label: '1级授权',
  418. value: '优易达(武汉)有限公司',
  419. editable: false,
  420. placeholder: ''
  421. }
  422. ]);
  423. const refreshAuthChainEditable = () => {
  424. if (!authChainLevels.value?.length) return;
  425. authChainLevels.value.forEach((lvl, idx) => (lvl.editable = idx !== authChainLevels.value.length - 1));
  426. };
  427. refreshAuthChainEditable();
  428. // 第一行的分类列表
  429. const category1List = ref<CategoryVO[]>([]); // 一级分类
  430. const category2List = ref<CategoryVO[]>([]); // 二级分类
  431. const category3List = ref<CategoryVO[]>([]); // 三级分类
  432. // 额外添加的分类行(只有二级和三级)
  433. interface ExtraCategoryRow {
  434. id: number;
  435. category2: string | number;
  436. category3: string | number;
  437. category2List: CategoryVO[];
  438. category3List: CategoryVO[];
  439. }
  440. const extraCategoryRows = ref<ExtraCategoryRow[]>([]);
  441. let extraCategoryRowIdCounter = 0;
  442. // 资质文件列表
  443. const fileList = ref([
  444. {
  445. index: 1,
  446. typeName: '商标注册证',
  447. fileOssIds: '',
  448. expireDate: '',
  449. fileDetails: [] as any[] // 缓存文件详情
  450. },
  451. {
  452. index: 2,
  453. typeName: '品牌授权书',
  454. fileOssIds: '',
  455. expireDate: '',
  456. fileDetails: [] as any[]
  457. },
  458. {
  459. index: 3,
  460. typeName: '质检报告',
  461. fileOssIds: '',
  462. expireDate: '',
  463. fileDetails: [] as any[]
  464. }
  465. ]);
  466. // 文件名称缓存
  467. const fileNamesCache = ref<Map<string, string[]>>(new Map());
  468. // 文件上传对话框
  469. const fileUploadDialogVisible = ref(false);
  470. const currentFileRow = ref<any>({});
  471. // 授权区域列表
  472. const areaList = ref<any[]>([]);
  473. // 授权区域对话框相关
  474. const areaDialogVisible = ref(false);
  475. const areaSubmitLoading = ref(false);
  476. const selectedAreas = ref<any[]>([]); // 选中的授权区域(级联选择器的值)
  477. const areaOptions = ref<any[]>([]); // 授权区域选项(从接口获取)
  478. const selectedAreaIds = ref<string[]>([]); // 选中的区域ID列表(用于提交)
  479. const cascaderProps = {
  480. multiple: true,
  481. value: 'value', // 使用 component 自带的 value(即行政区划代码如 110000)
  482. label: 'label',
  483. children: 'children',
  484. checkStrictly: false,
  485. emitPath: true
  486. };
  487. const toProvinceCityOptions = (options: any[]) => {
  488. return (options || []).map((province) => {
  489. const provinceValue6 = String(province.value ?? '');
  490. const provinceValue = provinceValue6.length >= 2 ? provinceValue6.slice(0, 2) : provinceValue6;
  491. const cities = (province.children || []).map((city: any) => {
  492. const cityValue6 = String(city.value ?? '');
  493. const cityValue = cityValue6.length >= 4 ? cityValue6.slice(0, 4) : cityValue6;
  494. const { children, ...rest } = city;
  495. return {
  496. ...rest,
  497. value: cityValue
  498. };
  499. });
  500. return {
  501. ...province,
  502. value: provinceValue,
  503. children: cities
  504. };
  505. });
  506. };
  507. const isCityCode = (val: any) => {
  508. const s = String(val ?? '').trim();
  509. return /^\d{4}$/.test(s);
  510. };
  511. /** 返回 */
  512. const goBack = () => {
  513. router.back();
  514. };
  515. /** 加载详情数据(编辑模式) */
  516. const loadDetailData = async () => {
  517. try {
  518. console.log('=== 加载详情数据 ===');
  519. console.log('编辑ID:', editId.value);
  520. const res = await getBrandAuthorizeDetail(editId.value);
  521. const data = res.data;
  522. console.log('详情数据:', data);
  523. // 回显基本信息
  524. formData.value.brandNo = (data as any).brandNo || '';
  525. formData.value.brandName = data.brandName || '';
  526. formData.value.brandLogo = data.brandLogo || '';
  527. formData.value.brandRegistrant = data.brandRegistrant || (data as any).registrationCertificate || '';
  528. formData.value.authorizeTypeId = Number(data.authorizeType) || '';
  529. formData.value.authorizeLevel = data.authorizeLevel || '';
  530. // 根据品牌名称找到对应的品牌ID(需要等待品牌列表加载完成)
  531. if (data.brandName) {
  532. // 如果品牌列表还没加载完成,等待一下
  533. let retryCount = 0;
  534. while (brandList.value.length === 0 && retryCount < 10) {
  535. await new Promise(resolve => setTimeout(resolve, 100));
  536. retryCount++;
  537. }
  538. if (brandList.value.length > 0) {
  539. const matchedBrand = brandList.value.find(brand => brand.brandName === data.brandName);
  540. if (matchedBrand) {
  541. formData.value.brandId = matchedBrand.id;
  542. console.log('根据品牌名称找到品牌ID:', { brandName: data.brandName, brandId: matchedBrand.id });
  543. } else {
  544. // 如果找不到匹配的品牌,设置一个临时ID避免验证失败
  545. formData.value.brandId = 'edit_mode_brand_id';
  546. console.warn('未找到匹配的品牌,使用临时ID');
  547. }
  548. } else {
  549. formData.value.brandId = 'edit_mode_brand_id';
  550. console.warn('品牌列表未加载,使用临时ID');
  551. }
  552. }
  553. console.log('授权类型回显:', {
  554. 原始值: data.authorizeType,
  555. 转换后: Number(data.authorizeType),
  556. 类型: typeof Number(data.authorizeType)
  557. });
  558. // 回显分类信息(根据 categoryId 和 categorysMap)
  559. if (data.categoryId && data.categorysMap) {
  560. // 设置三级分类ID(这是实际需要提交的)
  561. formData.value.category3 = data.categoryId;
  562. // 为了通过验证,也设置一级和二级分类的临时值
  563. formData.value.category1 = 'edit_mode_category1';
  564. formData.value.category2 = 'edit_mode_category2';
  565. // 存储分类名称用于显示
  566. categoryDisplayNames.value = {
  567. oneLevelName: data.categorysMap.oneLevelName || '',
  568. twoLevelName: data.categorysMap.twoLevelName || '',
  569. threeLevelName: data.categorysMap.threeLevelName || ''
  570. };
  571. console.log('分类信息回显:', {
  572. categoryId: data.categoryId,
  573. names: categoryDisplayNames.value,
  574. formData: {
  575. category1: formData.value.category1,
  576. category2: formData.value.category2,
  577. category3: formData.value.category3
  578. }
  579. });
  580. }
  581. // 回显授权链路
  582. if (data.brandLicensor) {
  583. const licensors = data.brandLicensor.split(',');
  584. authChainLevels.value = [];
  585. licensors.forEach((licensor, index) => {
  586. authChainLevels.value.push({
  587. id: authLevelIdCounter++,
  588. label: `${index}级授权`,
  589. value: licensor.trim(),
  590. editable: index !== licensors.length - 1,
  591. placeholder: '请输入授权人'
  592. });
  593. });
  594. console.log('授权链路回显:', authChainLevels.value);
  595. }
  596. // 回显授权区域
  597. if (data.authorizedArea) {
  598. const areaIds = data.authorizedArea.split(',');
  599. selectedAreaIds.value = areaIds;
  600. // 为级联选择器设置选中值
  601. // 需要根据区域ID在 areaOptions 中找到对应的省市关系
  602. selectedAreas.value = await buildCascaderValues(areaIds);
  603. areaList.value = buildAreaRows(selectedAreas.value);
  604. console.log('授权区域回显:', {
  605. areaIds,
  606. selectedAreas: selectedAreas.value,
  607. rows: areaList.value
  608. });
  609. }
  610. // 回显资质文件
  611. if (data.qualificationFiles && data.qualificationFiles.length > 0) {
  612. // 按文件类型分组
  613. const filesByType = {
  614. '商标注册证': [],
  615. '品牌授权书': [],
  616. '质检报告': []
  617. };
  618. data.qualificationFiles.forEach(file => {
  619. if (filesByType[file.name]) {
  620. filesByType[file.name].push(file);
  621. }
  622. });
  623. // 回显到对应的文件列表
  624. fileList.value.forEach(fileRow => {
  625. const files = filesByType[fileRow.typeName];
  626. if (files && files.length > 0) {
  627. // 构造 OSS ID 字符串(模拟上传组件的格式)
  628. const ossIds = files.map(f => f.id).join(',');
  629. fileRow.fileOssIds = ossIds;
  630. // 缓存文件详情
  631. fileRow.fileDetails = files.map(f => ({
  632. id: f.id,
  633. originalName: f.fileName,
  634. url: f.fileUrl,
  635. fileSuffix: f.fileType
  636. }));
  637. // 设置到期日期(使用第一个文件的到期日期)
  638. if (files[0].endTime) {
  639. fileRow.expireDate = files[0].endTime.split(' ')[0]; // 只取日期部分
  640. }
  641. // 缓存文件名称
  642. const fileNames = files.map(f => f.fileName);
  643. fileNamesCache.value.set(ossIds, fileNames);
  644. console.log(`${fileRow.typeName} 文件回显:`, {
  645. ossIds,
  646. fileNames,
  647. fileDetails: fileRow.fileDetails,
  648. expireDate: fileRow.expireDate
  649. });
  650. }
  651. });
  652. console.log('资质文件回显完成:', fileList.value);
  653. }
  654. ElMessage.success('数据加载成功');
  655. } catch (error) {
  656. console.error('加载详情数据失败:', error);
  657. ElMessage.error('加载数据失败');
  658. }
  659. };
  660. const handleBrandClear = () => {
  661. formData.value.brandId = '';
  662. formData.value.brandNo = '';
  663. formData.value.brandName = '';
  664. formData.value.brandLogo = '';
  665. formData.value.brandRegistrant = '';
  666. };
  667. /** 根据三级分类ID加载分类信息 */
  668. const loadCategoryByThirdLevel = async (categoryId: string | number) => {
  669. // 这里需要根据三级分类ID反向查找一级、二级分类
  670. // 暂时留空,后续需要完善
  671. console.log('需要根据三级分类ID加载分类信息:', categoryId);
  672. };
  673. /** 获取品牌列表 */
  674. const getBrandList = async () => {
  675. try {
  676. const res = await listBrand({
  677. pageNum: 1,
  678. pageSize: 1000
  679. });
  680. brandList.value = res.rows || [];
  681. console.log('品牌列表:', brandList.value);
  682. } catch (e) {
  683. console.error('获取品牌列表失败:', e);
  684. }
  685. };
  686. /** 获取授权类型列表 */
  687. const getAuthorizeTypeList = async () => {
  688. try {
  689. const res = await listAuthorizetypeLevel({
  690. pageNum: 1,
  691. pageSize: 100
  692. });
  693. authorizeTypeList.value = res.rows || [];
  694. console.log('授权类型列表:', authorizeTypeList.value);
  695. } catch (e) {
  696. console.error('获取授权类型列表失败:', e);
  697. }
  698. };
  699. /** 授权类型改变 */
  700. const handleAuthorizeTypeChange = (id: string | number) => {
  701. const selected = authorizeTypeList.value.find(item => item.id === id);
  702. if (selected) {
  703. formData.value.authorizeLevel = String(selected.authorizeLevel);
  704. console.log('选择授权类型:', {
  705. id: id,
  706. authorizeType: selected.authorizeType,
  707. authorizeLevel: selected.authorizeLevel
  708. });
  709. }
  710. };
  711. /** 品牌选择改变 */
  712. const handleBrandChange = (brandName: string) => {
  713. if (!brandName) {
  714. formData.value.brandId = '';
  715. formData.value.brandNo = '';
  716. formData.value.brandName = '';
  717. formData.value.brandLogo = '';
  718. formData.value.brandRegistrant = '';
  719. return;
  720. }
  721. const selectedBrand = brandList.value.find(b => b.brandName === brandName);
  722. if (selectedBrand) {
  723. formData.value.brandId = selectedBrand.id;
  724. formData.value.brandNo = selectedBrand.brandNo || '';
  725. formData.value.brandName = selectedBrand.brandName;
  726. formData.value.brandLogo = selectedBrand.brandLogo || ''; // 保存品牌LOGO
  727. formData.value.brandRegistrant = selectedBrand.registrationCertificate || selectedBrand.brandRegistrant || '';
  728. console.log('选择品牌:', {
  729. brandId: formData.value.brandId,
  730. brandNo: formData.value.brandNo,
  731. brandName: formData.value.brandName,
  732. brandLogo: formData.value.brandLogo,
  733. brandRegistrant: formData.value.brandRegistrant
  734. });
  735. }
  736. };
  737. /** 获取一级分类列表 */
  738. const getCategory1List = async () => {
  739. try {
  740. const res = await listCategory({
  741. classLevel: 1,
  742. pageNum: 1,
  743. pageSize: 1000
  744. });
  745. category1List.value = res.rows || [];
  746. console.log('一级分类:', category1List.value);
  747. } catch (e) {
  748. console.error('获取一级分类失败:', e);
  749. }
  750. };
  751. /** 第一行:一级分类改变 */
  752. const handleCategory1Change = async (value: string | number) => {
  753. // 清空二级和三级分类
  754. formData.value.category2 = '';
  755. formData.value.category3 = '';
  756. category2List.value = [];
  757. category3List.value = [];
  758. // 清空所有额外的分类行
  759. extraCategoryRows.value = [];
  760. if (!value) return;
  761. // 根据一级分类ID查询二级分类
  762. try {
  763. const res = await listCategory({
  764. parentId: value,
  765. classLevel: 2,
  766. pageNum: 1,
  767. pageSize: 1000
  768. });
  769. category2List.value = res.rows || [];
  770. console.log('二级分类:', category2List.value);
  771. } catch (e) {
  772. console.error('获取二级分类失败:', e);
  773. }
  774. };
  775. /** 获取所有已选中的三级分类ID */
  776. const getSelectedCategory3Ids = (): (string | number)[] => {
  777. const ids: (string | number)[] = [];
  778. // 第一行的三级分类
  779. if (formData.value.category3) {
  780. ids.push(formData.value.category3);
  781. }
  782. // 额外行的三级分类
  783. extraCategoryRows.value.forEach(row => {
  784. if (row.category3) {
  785. ids.push(row.category3);
  786. }
  787. });
  788. return ids;
  789. };
  790. /** 第一行:二级分类改变 */
  791. const handleCategory2Change = async (value: string | number) => {
  792. // 清空三级分类
  793. formData.value.category3 = '';
  794. category3List.value = [];
  795. if (!value) return;
  796. // 根据二级分类ID查询三级分类
  797. try {
  798. const res = await listCategory({
  799. parentId: value,
  800. classLevel: 3,
  801. pageNum: 1,
  802. pageSize: 1000
  803. });
  804. // 过滤掉已选中的三级分类(排除当前行自己的选择)
  805. const selectedIds = extraCategoryRows.value
  806. .map(row => row.category3)
  807. .filter(id => id);
  808. category3List.value = (res.rows || []).filter(
  809. (item: CategoryVO) => !selectedIds.includes(item.id)
  810. );
  811. console.log('三级分类:', category3List.value);
  812. } catch (e) {
  813. console.error('获取三级分类失败:', e);
  814. }
  815. };
  816. /** 第一行:三级分类改变 */
  817. const handleCategory3Change = (value: string | number) => {
  818. if (!value) return;
  819. // 检查是否已经在额外行中选择了相同的三级分类
  820. const isDuplicate = extraCategoryRows.value.some(row => row.category3 === value);
  821. if (isDuplicate) {
  822. ElMessage.warning('该分类已经选择过了,请选择其他分类');
  823. formData.value.category3 = '';
  824. }
  825. };
  826. /** 额外行:二级分类改变 */
  827. const handleExtraCategory2Change = async (row: ExtraCategoryRow, value: string | number) => {
  828. // 清空三级分类
  829. row.category3 = '';
  830. row.category3List = [];
  831. if (!value) return;
  832. // 根据二级分类ID查询三级分类
  833. try {
  834. const res = await listCategory({
  835. parentId: value,
  836. classLevel: 3,
  837. pageNum: 1,
  838. pageSize: 1000
  839. });
  840. // 过滤掉已选中的三级分类(排除当前行自己的选择)
  841. const selectedIds = getSelectedCategory3Ids().filter(id => id !== row.category3);
  842. row.category3List = (res.rows || []).filter(
  843. (item: CategoryVO) => !selectedIds.includes(item.id)
  844. );
  845. console.log('额外行三级分类:', row.category3List);
  846. } catch (e) {
  847. console.error('获取三级分类失败:', e);
  848. }
  849. };
  850. /** 额外行:三级分类改变 */
  851. const handleExtraCategory3Change = (row: ExtraCategoryRow, value: string | number) => {
  852. if (!value) return;
  853. // 检查是否与第一行的三级分类重复
  854. if (formData.value.category3 === value) {
  855. ElMessage.warning('该分类已经选择过了,请选择其他分类');
  856. row.category3 = '';
  857. return;
  858. }
  859. // 检查是否与其他额外行的三级分类重复
  860. const isDuplicate = extraCategoryRows.value.some(r => r.id !== row.id && r.category3 === value);
  861. if (isDuplicate) {
  862. ElMessage.warning('该分类已经选择过了,请选择其他分类');
  863. row.category3 = '';
  864. }
  865. };
  866. /** 添加分类行(只添加二级和三级) */
  867. const handleAddCategory = async () => {
  868. // 必须先选择一级分类
  869. if (!formData.value.category1) {
  870. ElMessage.warning('请先选择一级分类');
  871. return;
  872. }
  873. // 使用第一行的二级分类列表
  874. extraCategoryRows.value.push({
  875. id: ++extraCategoryRowIdCounter,
  876. category2: '',
  877. category3: '',
  878. category2List: [...category2List.value], // 复用第一行的二级分类列表
  879. category3List: []
  880. });
  881. console.log('添加额外分类行,当前行数:', extraCategoryRows.value.length);
  882. };
  883. /** 删除额外分类行 */
  884. const handleDeleteCategory = (row: ExtraCategoryRow) => {
  885. const index = extraCategoryRows.value.findIndex(r => r.id === row.id);
  886. if (index > -1) {
  887. extraCategoryRows.value.splice(index, 1);
  888. ElMessage.success('删除成功');
  889. }
  890. };
  891. /** 添加授权人 */
  892. const handleAddPerson = () => {
  893. if (!formData.value.authPerson) {
  894. ElMessage.warning('请输入授权人');
  895. return;
  896. }
  897. ElMessage.success('授权人添加成功');
  898. };
  899. /** 删除授权人 */
  900. const handleDeletePerson = () => {
  901. formData.value.authPerson = '';
  902. ElMessage.success('删除成功');
  903. };
  904. /** 添加授权级别 */
  905. const handleAddAuthLevel = () => {
  906. if (authChainLevels.value.length >= 5) {
  907. ElMessage.warning('授权链路最多支持5个级别');
  908. return;
  909. }
  910. // 在最前面插入新的0级授权
  911. // 所有现有级别的标签都要+1
  912. const newLevels: AuthChainLevel[] = [
  913. {
  914. id: authLevelIdCounter++,
  915. label: '0级授权',
  916. value: '',
  917. editable: true,
  918. placeholder: '请输入授权人'
  919. }
  920. ];
  921. // 将现有的级别标签都+1
  922. authChainLevels.value.forEach((level, index) => {
  923. newLevels.push({
  924. id: level.id, // 保持唯一身份标识避免页面组件渲染错误
  925. label: `${index + 1}级授权`,
  926. value: level.value,
  927. editable: true,
  928. placeholder: level.placeholder
  929. });
  930. });
  931. // 仅最后一级不可编辑
  932. if (newLevels.length) {
  933. newLevels.forEach((lvl, idx) => (lvl.editable = idx !== newLevels.length - 1));
  934. }
  935. authChainLevels.value = newLevels;
  936. ElMessage.success('添加成功');
  937. };
  938. /** 删除授权级别 */
  939. const handleDeleteAuthLevel = () => {
  940. if (authChainLevels.value.length <= 2) {
  941. ElMessage.warning('至少保留两级授权');
  942. return;
  943. }
  944. // 删除第一个(0级授权)
  945. authChainLevels.value.shift();
  946. // 重新调整所有级别的标签
  947. authChainLevels.value = authChainLevels.value.map((level, index) => ({
  948. ...level,
  949. label: `${index}级授权`,
  950. editable: true
  951. }));
  952. // 仅最后一级不可编辑
  953. if (authChainLevels.value.length) {
  954. authChainLevels.value.forEach((lvl, idx) => (lvl.editable = idx !== authChainLevels.value.length - 1));
  955. }
  956. ElMessage.success('删除成功');
  957. };
  958. /** 获取文件名称列表 */
  959. const getFileNames = (ossIds: string): string[] => {
  960. if (!ossIds) return [];
  961. // 检查缓存
  962. if (fileNamesCache.value.has(ossIds)) {
  963. return fileNamesCache.value.get(ossIds)!;
  964. }
  965. // 在编辑模式下,尝试从 fileDetails 中获取文件名
  966. if (isEditMode.value) {
  967. const fileRow = fileList.value.find(row => row.fileOssIds === ossIds);
  968. if (fileRow && fileRow.fileDetails && fileRow.fileDetails.length > 0) {
  969. const fileNames = fileRow.fileDetails.map((detail: any) => detail.originalName);
  970. fileNamesCache.value.set(ossIds, fileNames);
  971. return fileNames;
  972. }
  973. }
  974. // 异步加载文件名称(新增模式或缓存未命中时)
  975. loadFileNames(ossIds);
  976. return [];
  977. };
  978. /** 异步加载文件名称 */
  979. const loadFileNames = async (ossIds: string) => {
  980. try {
  981. const res = await listByIds(ossIds);
  982. const fileNames = res.data.map((oss: any) => oss.originalName);
  983. fileNamesCache.value.set(ossIds, fileNames);
  984. } catch (e) {
  985. console.error('获取文件名称失败:', e);
  986. }
  987. };
  988. // 监听文件变化,更新文件名称
  989. watch(
  990. () => fileList.value.map(f => f.fileOssIds),
  991. (newValues, oldValues) => {
  992. newValues.forEach((ossIds, index) => {
  993. // 只有当ossIds变化且不为空时才加载
  994. if (ossIds && ossIds !== oldValues?.[index]) {
  995. // 在编辑模式下,如果已经有缓存的文件详情,就不要重新请求OSS
  996. if (isEditMode.value && fileList.value[index].fileDetails && fileList.value[index].fileDetails.length > 0) {
  997. console.log('编辑模式:跳过OSS请求,使用已缓存的文件详情');
  998. return;
  999. }
  1000. loadFileNames(ossIds);
  1001. // 同时加载文件详情并缓存
  1002. loadFileDetails(index, ossIds);
  1003. }
  1004. });
  1005. },
  1006. { deep: true }
  1007. );
  1008. /** 加载文件详情并缓存 */
  1009. const loadFileDetails = async (fileIndex: number, ossIds: string) => {
  1010. try {
  1011. const res = await listByIds(ossIds);
  1012. fileList.value[fileIndex].fileDetails = res.data || [];
  1013. console.log(`文件详情已缓存 [${fileList.value[fileIndex].typeName}]:`, res.data);
  1014. } catch (e) {
  1015. console.error('获取文件详情失败:', e);
  1016. }
  1017. };
  1018. /** 上传文件 */
  1019. const handleUploadFile = (row: any) => {
  1020. currentFileRow.value = row;
  1021. fileUploadDialogVisible.value = true;
  1022. };
  1023. /** 下载文件 */
  1024. const handleDownloadFile = async (row: any) => {
  1025. if (!row.fileOssIds) {
  1026. ElMessage.warning('暂无文件可下载');
  1027. return;
  1028. }
  1029. try {
  1030. const res = await listByIds(row.fileOssIds);
  1031. if (res.data && res.data.length > 0) {
  1032. // 下载所有文件
  1033. res.data.forEach((file: any) => {
  1034. const link = document.createElement('a');
  1035. link.href = file.url;
  1036. link.download = file.originalName;
  1037. link.target = '_blank';
  1038. document.body.appendChild(link);
  1039. link.click();
  1040. document.body.removeChild(link);
  1041. });
  1042. ElMessage.success('开始下载');
  1043. }
  1044. } catch (e) {
  1045. console.error('下载文件失败:', e);
  1046. ElMessage.error('下载失败');
  1047. }
  1048. };
  1049. /** 删除文件 */
  1050. const handleDeleteFile = (row: any) => {
  1051. ElMessageBox.confirm('确定要删除该资质的所有文件吗?', '提示', {
  1052. confirmButtonText: '确定',
  1053. cancelButtonText: '取消',
  1054. type: 'warning'
  1055. }).then(() => {
  1056. row.fileOssIds = '';
  1057. // 清除缓存
  1058. fileNamesCache.value.forEach((value, key) => {
  1059. if (key.includes(row.fileOssIds)) {
  1060. fileNamesCache.value.delete(key);
  1061. }
  1062. });
  1063. ElMessage.success('删除成功');
  1064. }).catch(() => {
  1065. // 取消删除
  1066. });
  1067. };
  1068. /** 根据区域ID构建级联选择器的值 */
  1069. const buildCascaderValues = async (areaIds: string[]): Promise<any[]> => {
  1070. const cascaderValues: any[] = [];
  1071. // 递归寻找属于叶子节点的路径
  1072. const findLeafPath = (options: any[], targetId: string, currentPath: string[]): boolean => {
  1073. for (const option of options) {
  1074. const path = [...currentPath, option.value];
  1075. if (option.value === targetId) {
  1076. // 找到了对应的ID,判断是否为叶子节点
  1077. // 因为 Cascader 的 checkStrictly: false, 父节点的显式勾选会导致级联其下所有节点
  1078. // 只有是叶子节点(即没有 children),我们才作为有效路径提供给 Cascader 回显
  1079. if (!option.children || option.children.length === 0) {
  1080. cascaderValues.push(path);
  1081. }
  1082. return true;
  1083. }
  1084. if (option.children && option.children.length > 0) {
  1085. if (findLeafPath(option.children, targetId, path)) {
  1086. return true;
  1087. }
  1088. }
  1089. }
  1090. return false;
  1091. };
  1092. // 遍历所有区域ID,仅当它代表叶子节点时才添加路径去激活回显
  1093. areaIds
  1094. .map((areaId) => String(areaId).trim())
  1095. .filter((strId) => strId && isCityCode(strId))
  1096. .forEach((cityId) => {
  1097. findLeafPath(areaOptions.value, cityId, []);
  1098. });
  1099. console.log('构建级联选择器值:', { areaIds, cascaderValues });
  1100. return cascaderValues;
  1101. };
  1102. /** 选择区域 */
  1103. const handleSelectArea = () => {
  1104. areaDialogVisible.value = true;
  1105. };
  1106. /** 初始化授权区域选项数据 */
  1107. const initAreaOptions = async () => {
  1108. try {
  1109. areaOptions.value = toProvinceCityOptions(regionData as any);
  1110. console.log('授权区域选项数据加载完成(使用 element-china-area-data):', areaOptions.value.length);
  1111. } catch (e) {
  1112. console.error('获取授权区域数据失败:', e);
  1113. }
  1114. };
  1115. /** 提交授权区域 */
  1116. const handleAreaSubmit = async () => {
  1117. try {
  1118. areaSubmitLoading.value = true;
  1119. console.log('=== 授权区域提交 ===');
  1120. console.log('selectedAreas.value:', selectedAreas.value);
  1121. const areaRows = buildAreaRows(selectedAreas.value);
  1122. // 提取所有选中的区域ID(包括省和市的ID),使用后端短编码格式(11、1101)
  1123. const areaIds: string[] = [];
  1124. selectedAreas.value.forEach(path => {
  1125. if (Array.isArray(path)) {
  1126. // path 是 [省ID, 市ID]
  1127. console.log('处理路径:', path);
  1128. path.forEach(id => {
  1129. console.log('提取ID:', id, '类型:', typeof id);
  1130. const strId = String(id).trim();
  1131. if (strId && !areaIds.includes(strId)) {
  1132. areaIds.push(strId);
  1133. }
  1134. });
  1135. }
  1136. });
  1137. selectedAreaIds.value = areaIds;
  1138. // 更新授权区域列表显示
  1139. areaList.value = areaRows;
  1140. console.log('选中的授权区域名称:', areaRows);
  1141. console.log('选中的区域ID列表(用于提交):', selectedAreaIds.value);
  1142. console.log('=== 授权区域提交完成 ===');
  1143. ElMessage.success('授权区域设置成功');
  1144. areaDialogVisible.value = false;
  1145. } catch (e) {
  1146. console.error('设置授权区域失败:', e);
  1147. ElMessage.error('设置失败');
  1148. } finally {
  1149. areaSubmitLoading.value = false;
  1150. }
  1151. };
  1152. const buildAreaRows = (selectedPaths: any[]) => {
  1153. const provinceMap = new Map<string, Set<string>>();
  1154. const normalizeCityDisplayName = (provinceName: string, cityName: string) => {
  1155. const p = String(provinceName || '');
  1156. const c = String(cityName || '');
  1157. if (!c) return '';
  1158. if (c === '市辖区') {
  1159. return p.endsWith('市') ? p : `${p}市`;
  1160. }
  1161. return c;
  1162. };
  1163. selectedPaths.forEach((path) => {
  1164. if (Array.isArray(path) && path.length >= 2) {
  1165. const [provinceId, cityId] = path;
  1166. const provinceName = findRegionNameById(provinceId, areaOptions.value);
  1167. const province = areaOptions.value.find((p) => p.value === String(provinceId));
  1168. const cityName = province && cityId ? findRegionNameById(cityId, province.children || []) : '';
  1169. if (!provinceName) return;
  1170. if (!provinceMap.has(provinceName)) {
  1171. provinceMap.set(provinceName, new Set());
  1172. }
  1173. const displayCityName = normalizeCityDisplayName(provinceName, cityName);
  1174. if (displayCityName) {
  1175. provinceMap.get(provinceName)!.add(displayCityName);
  1176. }
  1177. }
  1178. });
  1179. return Array.from(provinceMap.entries()).map(([province, cities]) => {
  1180. return {
  1181. province,
  1182. city: Array.from(cities).join(',')
  1183. };
  1184. });
  1185. };
  1186. /** 从级联选择器的值中提取区域数据 */
  1187. const extractRegionData = (selectedPaths: any[]) => {
  1188. // 用于存储省-市的映射关系
  1189. const provinceMap = new Map<string, Set<string>>();
  1190. selectedPaths.forEach(path => {
  1191. if (Array.isArray(path) && path.length >= 2) {
  1192. const [provinceId, cityId] = path;
  1193. // 查找对应的名称(通过value查找)
  1194. const provinceName = findRegionNameById(provinceId, areaOptions.value);
  1195. const province = areaOptions.value.find(p => p.value === provinceId);
  1196. const cityName = province && cityId ? findRegionNameById(cityId, province.children || []) : '';
  1197. if (provinceName) {
  1198. if (!provinceMap.has(provinceName)) {
  1199. provinceMap.set(provinceName, new Set());
  1200. }
  1201. // 直辖市的处理(市辖区转为原来的名称)
  1202. if (cityName && cityName !== '市辖区') {
  1203. provinceMap.get(provinceName)!.add(cityName);
  1204. } else if (cityName === '市辖区') {
  1205. provinceMap.get(provinceName)!.add(provinceName);
  1206. }
  1207. }
  1208. }
  1209. });
  1210. // 生成省份和城市字符串
  1211. const provinces: string[] = [];
  1212. const cities: string[] = [];
  1213. provinceMap.forEach((citySet, provinceName) => {
  1214. provinces.push(provinceName);
  1215. cities.push(Array.from(citySet).join(','));
  1216. });
  1217. return {
  1218. provinces: provinces.join(','),
  1219. cities: cities.join(';')
  1220. };
  1221. };
  1222. /** 根据value查找区域名称 */
  1223. const findRegionNameById = (id: string | number, regions: any[]): string => {
  1224. const targetId = String(id);
  1225. const region = regions.find(r => r.value === targetId);
  1226. return region ? region.label : '';
  1227. };
  1228. /** 提交 */
  1229. const handleSubmit = async () => {
  1230. try {
  1231. // 验证必填项
  1232. if (!formData.value.brandId || !formData.value.brandName) {
  1233. ElMessage.warning('请选择品牌');
  1234. return;
  1235. }
  1236. // 编辑模式下跳过分类验证,因为分类是锁死的
  1237. if (!isEditMode.value) {
  1238. if (!formData.value.category1 || !formData.value.category2 || !formData.value.category3) {
  1239. ElMessage.warning('请选择完整的分类');
  1240. return;
  1241. }
  1242. }
  1243. if (!userStore.supplierId) {
  1244. ElMessage.error('供应商ID不存在');
  1245. return;
  1246. }
  1247. // 收集所有分类数据
  1248. const allCategories = [
  1249. {
  1250. category1: formData.value.category1,
  1251. category2: formData.value.category2,
  1252. category3: formData.value.category3
  1253. },
  1254. ...extraCategoryRows.value.map(row => ({
  1255. category1: formData.value.category1,
  1256. category2: row.category2,
  1257. category3: row.category3
  1258. }))
  1259. ];
  1260. // 提取所有三级分类ID(只传三级分类ID的列表,并去重)
  1261. const categoryIds = [...new Set(
  1262. allCategories
  1263. .map(cat => cat.category3)
  1264. .filter(id => id) // 过滤掉空值
  1265. )];
  1266. // 验证是否有重复的分类
  1267. if (categoryIds.length !== allCategories.filter(cat => cat.category3).length) {
  1268. console.warn('检测到重复的分类选择,已自动去重');
  1269. }
  1270. // 构建授权区域字符串(使用ID,用逗号分隔)
  1271. const authorizedArea = selectedAreaIds.value.join(',');
  1272. console.log('=== 构建授权区域 ===');
  1273. console.log('selectedAreaIds.value:', selectedAreaIds.value);
  1274. console.log('authorizedArea (提交给后端):', authorizedArea);
  1275. console.log('类型检查 - 是否包含数字:', selectedAreaIds.value.map(id => ({ id, type: typeof id })));
  1276. // 构建授权链路字符串(从0级到最高级,用逗号分隔)
  1277. const brandLicensor = authChainLevels.value
  1278. .map(level => level.value)
  1279. .filter(value => value) // 过滤掉空值
  1280. .join(',');
  1281. // 构建资质文件集合(使用缓存的文件详情)
  1282. const qualificationFiles: QualificationFileForm[] = [];
  1283. for (const file of fileList.value) {
  1284. if (file.fileOssIds && file.fileDetails.length > 0) {
  1285. // 使用缓存的文件详情,无需再次请求
  1286. file.fileDetails.forEach((ossFile: any) => {
  1287. qualificationFiles.push({
  1288. name: file.typeName, // 资质类型名称(商标注册证、品牌授权书、质检报告)
  1289. fileName: ossFile.originalName, // 文件名称
  1290. fileType: ossFile.fileSuffix || '', // 文件类型
  1291. fileUrl: ossFile.url, // 文件URL
  1292. endTime: file.expireDate ? new Date(file.expireDate).toISOString() : '' // 资质到期日期
  1293. });
  1294. });
  1295. }
  1296. }
  1297. console.log('使用缓存的文件详情,无需等待OSS请求');
  1298. // 构建提交数据
  1299. const submitData: any = {
  1300. supplierId: userStore.supplierId,
  1301. supplierNo: userStore.supplierNo,
  1302. brandId: formData.value.brandId,
  1303. brandNo: formData.value.brandNo,
  1304. brandName: formData.value.brandName,
  1305. brandLogo: formData.value.brandLogo,
  1306. brandRegistrant: formData.value.brandRegistrant,
  1307. authorizeType: formData.value.authorizeTypeId, // 授权类型ID
  1308. authorizeLevel: formData.value.authorizeLevel, // 授权等级
  1309. categoryIds: categoryIds, // 三级分类ID列表
  1310. authorizedArea: authorizedArea, // 授权地址(区域ID列表)
  1311. brandLicensor: brandLicensor, // 授权链路(授权人列表)
  1312. qualificationFiles: qualificationFiles // 资质文件集合
  1313. };
  1314. console.log('提交数据:', submitData);
  1315. console.log('三级分类ID列表:', categoryIds);
  1316. console.log('授权地址:', authorizedArea);
  1317. console.log('授权链路:', brandLicensor);
  1318. console.log('资质文件集合:', qualificationFiles);
  1319. // 调用API(根据模式调用不同的接口)
  1320. if (isEditMode.value) {
  1321. // 编辑模式:调用更新接口
  1322. await updateSupplierauthorize({ ...submitData, id: editId.value });
  1323. ElMessage.success('更新成功');
  1324. } else {
  1325. // 新增模式:调用新增接口
  1326. await addSupplierauthorize(submitData);
  1327. ElMessage.success('提交成功');
  1328. }
  1329. router.back();
  1330. } catch (error) {
  1331. console.error('提交失败:', error);
  1332. ElMessage.error('提交失败,请重试');
  1333. }
  1334. };
  1335. onMounted(async () => {
  1336. console.log('=== Edit页面初始化 ===');
  1337. console.log('route.query:', route.query);
  1338. console.log('route.query.id:', route.query.id);
  1339. // 检查是否为编辑模式
  1340. if (route.query.id) {
  1341. editId.value = route.query.id as string;
  1342. isEditMode.value = true;
  1343. console.log('✓ 编辑模式,ID:', editId.value);
  1344. console.log('✓ isEditMode.value:', isEditMode.value);
  1345. // 编辑模式:并行加载基础数据和详情数据
  1346. await Promise.all([
  1347. getBrandList(),
  1348. getAuthorizeTypeList(),
  1349. getCategory1List(),
  1350. initAreaOptions(),
  1351. loadDetailData() // 详情数据也并行加载
  1352. ]);
  1353. } else {
  1354. console.log('✓ 新增模式');
  1355. console.log('✓ isEditMode.value:', isEditMode.value);
  1356. // 新增模式:只加载基础数据
  1357. await Promise.all([
  1358. getBrandList(),
  1359. getAuthorizeTypeList(),
  1360. getCategory1List(),
  1361. initAreaOptions()
  1362. ]);
  1363. }
  1364. console.log('=== Edit页面初始化完成 ===');
  1365. });
  1366. </script>
  1367. <style scoped>
  1368. .app-container {
  1369. background: #f0f2f5;
  1370. min-height: 100vh;
  1371. }
  1372. .page-header {
  1373. background: #fff;
  1374. padding: 16px 24px;
  1375. display: flex;
  1376. align-items: center;
  1377. border-bottom: 1px solid #e8e8e8;
  1378. }
  1379. .back-icon {
  1380. font-size: 18px;
  1381. cursor: pointer;
  1382. margin-right: 12px;
  1383. color: #666;
  1384. }
  1385. .back-icon:hover {
  1386. color: #409eff;
  1387. }
  1388. .page-title {
  1389. font-size: 16px;
  1390. font-weight: 500;
  1391. color: #333;
  1392. }
  1393. .form-section {
  1394. background: #fff;
  1395. margin: 20px;
  1396. padding: 24px;
  1397. border-radius: 4px;
  1398. }
  1399. .section-title {
  1400. font-size: 16px;
  1401. font-weight: 500;
  1402. color: #333;
  1403. margin-bottom: 24px;
  1404. padding-bottom: 12px;
  1405. border-bottom: 1px solid #e8e8e8;
  1406. }
  1407. .auth-form {
  1408. margin-bottom: 30px;
  1409. }
  1410. .extra-category-row {
  1411. margin-bottom: 16px;
  1412. }
  1413. .file-section,
  1414. .area-section {
  1415. margin-top: 30px;
  1416. }
  1417. .file-title,
  1418. .area-title {
  1419. font-size: 14px;
  1420. font-weight: 500;
  1421. color: #333;
  1422. margin-bottom: 16px;
  1423. display: flex;
  1424. align-items: center;
  1425. justify-content: space-between;
  1426. }
  1427. .empty-tip {
  1428. text-align: center;
  1429. padding: 40px;
  1430. color: #999;
  1431. }
  1432. .submit-section {
  1433. margin-top: 30px;
  1434. text-align: center;
  1435. }
  1436. .file-name-list {
  1437. text-align: left;
  1438. padding: 8px 0;
  1439. }
  1440. .file-name-item {
  1441. padding: 4px 0;
  1442. color: #409eff;
  1443. font-size: 14px;
  1444. line-height: 1.5;
  1445. }
  1446. .file-actions {
  1447. display: flex;
  1448. justify-content: center;
  1449. align-items: center;
  1450. gap: 8px;
  1451. }
  1452. .auth-chain-container {
  1453. width: 100%;
  1454. }
  1455. .auth-chain-actions {
  1456. display: flex;
  1457. gap: 12px;
  1458. }
  1459. .auth-chain-custom-table {
  1460. width: 100%;
  1461. margin-bottom: 16px;
  1462. border: 1px solid #ebeef5;
  1463. border-radius: 4px;
  1464. overflow-x: auto;
  1465. }
  1466. .auth-chain-header {
  1467. display: flex;
  1468. background-color: #f5f7fa;
  1469. border-bottom: 1px solid #ebeef5;
  1470. }
  1471. .auth-chain-row {
  1472. display: flex;
  1473. }
  1474. .auth-chain-cell {
  1475. flex: 1;
  1476. min-width: 200px;
  1477. padding: 12px;
  1478. text-align: center;
  1479. border-right: 1px solid #ebeef5;
  1480. box-sizing: border-box;
  1481. }
  1482. .auth-chain-cell:last-child {
  1483. border-right: none;
  1484. }
  1485. .header-cell {
  1486. font-weight: bold;
  1487. color: #909399;
  1488. font-size: 14px;
  1489. }
  1490. /* 只读输入框样式 - 更美观的设计 */
  1491. .readonly-input :deep(.el-input__wrapper) {
  1492. background: var(--el-input-bg-color) !important;
  1493. border: 1px solid var(--el-border-color) !important;
  1494. box-shadow: none !important;
  1495. cursor: default !important;
  1496. }
  1497. .readonly-input :deep(.el-input__inner) {
  1498. background: transparent !important;
  1499. color: var(--el-text-color-regular) !important;
  1500. font-weight: 400 !important;
  1501. /* 文字居中显示 */
  1502. text-align: left !important;
  1503. }
  1504. .readonly-input :deep(.el-input__wrapper):hover {
  1505. border-color: var(--el-border-color) !important;
  1506. box-shadow: none !important;
  1507. }
  1508. .readonly-input :deep(.el-input__wrapper):focus-within {
  1509. border-color: var(--el-border-color) !important;
  1510. box-shadow: none !important;
  1511. }
  1512. .auth-form :deep(.el-input.is-disabled .el-input__wrapper),
  1513. .auth-form :deep(.el-select .el-input.is-disabled .el-input__wrapper) {
  1514. cursor: default;
  1515. }
  1516. .readonly-not-allowed :deep(.el-input__wrapper) {
  1517. cursor: not-allowed !important;
  1518. pointer-events: auto;
  1519. }
  1520. .readonly-not-allowed :deep(.el-input__inner) {
  1521. cursor: default !important;
  1522. }
  1523. /* 分类区域样式 */
  1524. .category-section {
  1525. margin-bottom: 20px;
  1526. padding: 16px;
  1527. background: #fafbfc;
  1528. border-radius: 8px;
  1529. border: 1px solid #e1e4e8;
  1530. }
  1531. .section-label {
  1532. display: flex;
  1533. align-items: center;
  1534. justify-content: space-between;
  1535. margin-bottom: 16px;
  1536. font-weight: 600;
  1537. color: #24292e;
  1538. }
  1539. </style>