detail.vue 98 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210
  1. <template>
  2. <div class="app-container">
  3. <!-- 页面头部 -->
  4. <div class="detail-header">
  5. <el-icon class="back-icon" @click="goBack"><ArrowLeft /></el-icon>
  6. <span class="header-title">查看供应商</span>
  7. </div>
  8. <!-- 标签页 -->
  9. <el-tabs v-model="activeTab" class="detail-tabs">
  10. <!-- 基本信息 -->
  11. <el-tab-pane label="基本信息" name="basic">
  12. <BasicInfoTab
  13. v-model:selectedOfficeRegion="selectedOfficeRegion"
  14. :detailData="detailData"
  15. :companyOptions="companyOptions"
  16. :enterpriseScaleOptions="enterpriseScaleOptions"
  17. :industryCategoryOptions="industryCategoryOptions"
  18. :supplierLevelOptions="supplierLevelOptions"
  19. :supplierTypeOptions="supplierTypeOptions"
  20. :isAddMode="isAddMode"
  21. :isViewMode="isViewMode"
  22. :isBasicInfoSaved="isBasicInfoSaved"
  23. :businessInfo="businessInfo"
  24. :businessInfoLoading="businessInfoLoading"
  25. :regionOptions="regionOptions"
  26. :paymentInfoList="paymentInfoList"
  27. :formatDate="formatDate"
  28. @save="handleSave"
  29. @getBusinessInfo="handleGetBusinessInfo"
  30. @officeRegionChange="handleOfficeRegionChange"
  31. @addPayment="handleAddPayment"
  32. @viewPayment="handleViewPayment"
  33. @editPayment="handleEditPayment"
  34. />
  35. </el-tab-pane>
  36. <!-- 采购信息 -->
  37. <el-tab-pane label="采销信息" name="purchase" :disabled="isAddMode && !isBasicInfoSaved">
  38. <PurchaseInfoTab
  39. v-model:selectedProductManager="selectedProductManager"
  40. v-model:selectedBuyer="selectedBuyer"
  41. :staffOptions="staffOptions"
  42. :isViewMode="isViewMode"
  43. @save="handleSavePurchaseInfo"
  44. />
  45. </el-tab-pane>
  46. <!-- 联系人 -->
  47. <el-tab-pane label="联系人" name="contact" :disabled="isAddMode && !isBasicInfoSaved">
  48. <ContactTab
  49. :contactSearchParams="contactSearchParams"
  50. :contactList="contactList"
  51. :contactLoading="contactLoading"
  52. :isViewMode="isViewMode"
  53. @search="handleContactSearch"
  54. @reset="handleContactReset"
  55. @add="handleAddContact"
  56. @view="handleViewContact"
  57. @edit="handleEditContact"
  58. />
  59. </el-tab-pane>
  60. <!-- 供应信息 -->
  61. <el-tab-pane label="供应信息" name="supply" :disabled="isAddMode && !isBasicInfoSaved">
  62. <SupplyInfoTab
  63. v-model:selectedCategories="selectedCategories"
  64. :isViewMode="isViewMode"
  65. :productCategoryList="productCategoryList"
  66. :selectedBrands="selectedBrands"
  67. :supplyAreaList="supplyAreaList"
  68. :authorizationList="authorizationList"
  69. :authorizationPagination="authorizationPagination"
  70. :formatDate="formatDate"
  71. :getAuthorizedStatusText="getAuthorizedStatusText"
  72. @saveCategories="handleSaveCategories"
  73. @addBrand="handleAddBrand"
  74. @removeBrand="handleRemoveBrand"
  75. @editSupplyArea="handleEditSupplyArea"
  76. @authorizationPagination="getAuthorizationList"
  77. />
  78. </el-tab-pane>
  79. <!-- 地址管理 -->
  80. <el-tab-pane label="地址管理" name="address" :disabled="isAddMode && !isBasicInfoSaved">
  81. <AddressTab
  82. :addressList="addressList"
  83. :isViewMode="isViewMode"
  84. @add="handleAddAddress"
  85. @edit="handleEditAddress"
  86. @delete="handleDeleteAddress"
  87. />
  88. </el-tab-pane>
  89. <!-- 合同管理 -->
  90. <el-tab-pane label="合同管理" name="contract" :disabled="isAddMode && !isBasicInfoSaved">
  91. <ContractTab
  92. :contractSearchParams="contractSearchParams"
  93. :contractList="contractList"
  94. :contractPagination="contractPagination"
  95. :isViewMode="isViewMode"
  96. :contract-type-dict="contractTypeDict"
  97. :contract-status-dict="contractStatusDict"
  98. :formatDate="formatDate"
  99. :getContractTypeText="getContractTypeText"
  100. :getContractStatusText="getContractStatusText"
  101. @search="handleContractSearch"
  102. @reset="handleContractReset"
  103. @add="handleAddContract"
  104. @viewAttachment="handleViewAttachment"
  105. @view="handleViewContract"
  106. @edit="handleEditContract"
  107. @sizeChange="handleContractSizeChange"
  108. @currentChange="handleContractCurrentChange"
  109. />
  110. </el-tab-pane>
  111. </el-tabs>
  112. <!-- 付款信息对话框 -->
  113. <el-dialog
  114. v-model="paymentDialogVisible"
  115. :title="paymentDialogTitle"
  116. width="1000px"
  117. :close-on-click-modal="false"
  118. >
  119. <el-form
  120. ref="paymentFormRef"
  121. :model="paymentForm"
  122. :rules="paymentFormRules"
  123. label-width="140px"
  124. :disabled="paymentDialogReadonly"
  125. >
  126. <el-form-item label="开票类型:" prop="invoiceTypeNo">
  127. <el-select
  128. v-model="paymentForm.invoiceTypeNo"
  129. placeholder="请选择"
  130. style="width: 100%;"
  131. @change="handleInvoiceTypeChange"
  132. >
  133. <el-option
  134. v-for="item in invoiceTypeList"
  135. :key="item.id"
  136. :label="item.invoiceTypeName"
  137. :value="item.id"
  138. />
  139. </el-select>
  140. </el-form-item>
  141. <el-form-item label="发票抬头:" prop="businessName">
  142. <el-input v-model="paymentForm.businessName" placeholder="企业工商名称" disabled/>
  143. </el-form-item>
  144. <el-form-item label="纳税人识别号:" prop="circlesName">
  145. <el-input v-model="paymentForm.circlesName" placeholder="请输入纳税人识别号" disabled/>
  146. </el-form-item>
  147. <el-form-item label="开户行行号:" prop="bankNum">
  148. <el-input v-model="paymentForm.bankNum" placeholder="请输入开户行行号" />
  149. </el-form-item>
  150. <el-form-item label="开户行名称:" prop="bankInfoNo">
  151. <el-select
  152. v-model="paymentForm.bankInfoNo"
  153. placeholder="请选择"
  154. style="width: 100%;"
  155. @change="handleBankChange"
  156. >
  157. <el-option
  158. v-for="item in systemBankList"
  159. :key="item.id"
  160. :label="item.bnName"
  161. :value="item.id"
  162. />
  163. </el-select>
  164. </el-form-item>
  165. <el-form-item label="银行账户:" prop="bankNo">
  166. <el-input v-model="paymentForm.bankNo" placeholder="请输入银行账户" />
  167. </el-form-item>
  168. <el-form-item label="固定电话:" prop="phone">
  169. <el-input v-model="paymentForm.phone" placeholder="请输入固定电话" />
  170. </el-form-item>
  171. <el-form-item label="地址:" prop="businessAddress">
  172. <el-input v-model="paymentForm.businessAddress" placeholder="工商地址" disabled />
  173. </el-form-item>
  174. <el-form-item label="是否主账号:" prop="num">
  175. <el-radio-group v-model="paymentForm.num">
  176. <el-radio :label="1">是</el-radio>
  177. <el-radio :label="0">否</el-radio>
  178. </el-radio-group>
  179. </el-form-item>
  180. </el-form>
  181. <template #footer>
  182. <div class="dialog-footer">
  183. <el-button @click="paymentDialogVisible = false">取消</el-button>
  184. <el-button v-if="!paymentDialogReadonly" type="primary" @click="handlePaymentSubmit" :loading="paymentSubmitLoading">确定</el-button>
  185. </div>
  186. </template>
  187. </el-dialog>
  188. <!-- 联系人对话框 -->
  189. <el-dialog
  190. v-model="contactDialogVisible"
  191. :title="contactDialogTitle"
  192. width="900px"
  193. :close-on-click-modal="false"
  194. >
  195. <el-form
  196. ref="contactFormRef"
  197. :model="contactForm"
  198. :rules="contactFormRules"
  199. label-width="140px"
  200. :disabled="contactDialogReadonly"
  201. >
  202. <el-row :gutter="20">
  203. <el-col :span="12">
  204. <el-form-item label="员工姓名:" prop="userName">
  205. <el-input v-model="contactForm.userName" placeholder="请输入员工姓名" />
  206. </el-form-item>
  207. </el-col>
  208. <el-col :span="12">
  209. <el-form-item label="手机号:" prop="phone">
  210. <el-input
  211. v-model="contactForm.phone"
  212. placeholder="请输入手机号"
  213. maxlength="11"
  214. @input="onContactPhoneInput"
  215. />
  216. </el-form-item>
  217. </el-col>
  218. </el-row>
  219. <el-row :gutter="20">
  220. <el-col :span="12">
  221. <el-form-item label="部门:" prop="departmentNo">
  222. <el-input v-model="contactForm.departmentNo" placeholder="请输入部门" />
  223. </el-form-item>
  224. </el-col>
  225. <el-col :span="12">
  226. <el-form-item label="职位:" prop="position">
  227. <el-input v-model="contactForm.position" placeholder="请输入职位" />
  228. </el-form-item>
  229. </el-col>
  230. </el-row>
  231. <el-row :gutter="20">
  232. <el-col :span="12">
  233. <el-form-item label="角色:" prop="roleNo">
  234. <el-input v-model="contactForm.roleNo" placeholder="请输入角色" />
  235. </el-form-item>
  236. </el-col>
  237. <el-col :span="12">
  238. <el-form-item label="邮箱:" prop="email">
  239. <el-input v-model="contactForm.email" placeholder="请输入邮箱" />
  240. </el-form-item>
  241. </el-col>
  242. </el-row>
  243. <el-row :gutter="20">
  244. <el-col :span="12">
  245. <el-form-item label="主要联系人:" prop="isPrimaryContact">
  246. <el-select v-model="contactForm.isPrimaryContact" placeholder="请选择" style="width: 100%;">
  247. <el-option label="是" value="1" />
  248. <el-option label="否" value="0" />
  249. </el-select>
  250. </el-form-item>
  251. </el-col>
  252. <el-col :span="12">
  253. <el-form-item label="允许登录供应商端:" prop="isRegister">
  254. <el-select v-model="contactForm.isRegister" placeholder="请选择" style="width: 100%;">
  255. <el-option label="是" value="1" />
  256. <el-option label="否" value="0" />
  257. </el-select>
  258. </el-form-item>
  259. </el-col>
  260. </el-row>
  261. <el-row :gutter="20">
  262. <el-col :span="12">
  263. <el-form-item label="传真:" prop="fax">
  264. <el-input v-model="contactForm.fax" placeholder="请输入传真" />
  265. </el-form-item>
  266. </el-col>
  267. </el-row>
  268. <el-row :gutter="20">
  269. <el-col :span="24">
  270. <el-form-item label="备注:" prop="remark">
  271. <el-input
  272. v-model="contactForm.remark"
  273. type="textarea"
  274. :rows="3"
  275. placeholder="请输入备注"
  276. />
  277. </el-form-item>
  278. </el-col>
  279. </el-row>
  280. </el-form>
  281. <template #footer>
  282. <div class="dialog-footer">
  283. <el-button @click="contactDialogVisible = false">取消</el-button>
  284. <el-button v-if="!contactDialogReadonly" type="primary" @click="handleContactSubmit" :loading="contactSubmitLoading">确定</el-button>
  285. </div>
  286. </template>
  287. </el-dialog>
  288. <!-- 供货区域编辑对话框 -->
  289. <el-dialog
  290. v-model="supplyAreaDialogVisible"
  291. title="编辑供货区域"
  292. width="700px"
  293. :close-on-click-modal="false"
  294. >
  295. <RegionCascader v-model="selectedSupplyAreaCodes" :multiple="true" :options="supplyAreaOptions" :show-district="false" />
  296. <template #footer>
  297. <div class="dialog-footer">
  298. <el-button @click="supplyAreaDialogVisible = false">取消</el-button>
  299. <el-button type="primary" @click="handleSupplyAreaSubmit" :loading="supplyAreaSubmitLoading">确定</el-button>
  300. </div>
  301. </template>
  302. </el-dialog>
  303. <!-- 地址管理对话框 -->
  304. <el-dialog
  305. v-model="addressDialogVisible"
  306. :title="addressDialogTitle"
  307. width="650px"
  308. :close-on-click-modal="false"
  309. >
  310. <el-form
  311. ref="addressFormRef"
  312. :model="addressForm"
  313. :rules="addressFormRules"
  314. label-width="120px"
  315. :disabled="addressDialogReadonly"
  316. >
  317. <el-row :gutter="20">
  318. <el-col :span="12">
  319. <el-form-item label="供应商编号:">
  320. <el-input v-model="addressForm.supplierNo" disabled />
  321. </el-form-item>
  322. </el-col>
  323. <el-col :span="12">
  324. <el-form-item label="收货人:" prop="shipperName" required>
  325. <el-input v-model="addressForm.shipperName" placeholder="请输入收货人" />
  326. </el-form-item>
  327. </el-col>
  328. </el-row>
  329. <el-row :gutter="20">
  330. <el-col :span="12">
  331. <el-form-item label="手机号码:" prop="shipperPhone" required>
  332. <el-input v-model="addressForm.shipperPhone" placeholder="请输入手机号码" />
  333. </el-form-item>
  334. </el-col>
  335. <el-col :span="12">
  336. <el-form-item label="邮政编码:" prop="shippingPostCode">
  337. <el-input v-model="addressForm.shippingPostCode" placeholder="请输入邮政编码" />
  338. </el-form-item>
  339. </el-col>
  340. </el-row>
  341. <el-row :gutter="20">
  342. <el-col :span="24">
  343. <el-form-item label="地址:" prop="shippingProvincial" required>
  344. <el-cascader
  345. v-model="selectedAddressRegion"
  346. :options="regionOptions"
  347. placeholder="请选择"
  348. clearable
  349. filterable
  350. style="width: 100%;"
  351. @change="handleAddressRegionChange"
  352. />
  353. </el-form-item>
  354. </el-col>
  355. </el-row>
  356. <el-row :gutter="20">
  357. <el-col :span="24">
  358. <el-form-item label="详细地址:" prop="shippingAddress" required>
  359. <el-input
  360. v-model="addressForm.shippingAddress"
  361. type="textarea"
  362. :rows="3"
  363. placeholder="请输入详细地址"
  364. />
  365. </el-form-item>
  366. </el-col>
  367. </el-row>
  368. <el-row :gutter="20">
  369. <el-col :span="12">
  370. <el-form-item label="默认地址:">
  371. <el-switch v-model="addressForm.isSelf" :active-value="1" :inactive-value="0" />
  372. </el-form-item>
  373. </el-col>
  374. </el-row>
  375. </el-form>
  376. <template #footer>
  377. <div class="dialog-footer">
  378. <el-button @click="addressDialogVisible = false">取消</el-button>
  379. <el-button v-if="!addressDialogReadonly" type="primary" @click="handleAddressSubmit" :loading="addressSubmitLoading">确定</el-button>
  380. </div>
  381. </template>
  382. </el-dialog>
  383. <!-- 合同管理对话框 -->
  384. <el-dialog
  385. v-model="contractDialogVisible"
  386. :title="contractDialogTitle"
  387. width="900px"
  388. :close-on-click-modal="false"
  389. >
  390. <el-form
  391. ref="contractFormRef"
  392. :model="contractForm"
  393. :rules="contractFormRules"
  394. label-width="140px"
  395. :disabled="contractDialogReadonly"
  396. >
  397. <el-row :gutter="20">
  398. <el-col :span="24">
  399. <el-form-item label="合同名称:" prop="contractName">
  400. <el-input v-model="contractForm.contractName" placeholder="请输入合同名称" />
  401. </el-form-item>
  402. </el-col>
  403. </el-row>
  404. <el-row :gutter="20">
  405. <el-col :span="12">
  406. <el-form-item label="合同类型:" prop="contractType">
  407. <el-select v-model="contractForm.contractType" placeholder="请选择" style="width: 100%;">
  408. <el-option
  409. v-for="item in contractTypeDict"
  410. :key="item.dictValue"
  411. :label="item.dictLabel"
  412. :value="item.dictValue"
  413. />
  414. </el-select>
  415. </el-form-item>
  416. </el-col>
  417. <el-col :span="12">
  418. <el-form-item label="提醒时间:" prop="demandReminderTime">
  419. <el-input-number
  420. v-model="contractForm.demandReminderTime"
  421. :min="1"
  422. :max="365"
  423. style="width: 150px;"
  424. />
  425. <span style="margin-left: 10px;">天</span>
  426. </el-form-item>
  427. </el-col>
  428. </el-row>
  429. <el-row :gutter="20">
  430. <el-col :span="12">
  431. <el-form-item label="开始时间:" prop="contractStartTime">
  432. <el-date-picker
  433. v-model="contractForm.contractStartTime"
  434. type="date"
  435. placeholder="请选择"
  436. style="width: 100%;"
  437. />
  438. </el-form-item>
  439. </el-col>
  440. <el-col :span="12">
  441. <el-form-item label="截止时间:" prop="contractEndTime">
  442. <el-date-picker
  443. v-model="contractForm.contractEndTime"
  444. type="date"
  445. placeholder="请选择"
  446. style="width: 100%;"
  447. />
  448. </el-form-item>
  449. </el-col>
  450. </el-row>
  451. <el-row :gutter="20">
  452. <el-col :span="12">
  453. <el-form-item label="开票类型:" prop="invoiceType">
  454. <el-select v-model="contractForm.invoiceType" placeholder="请选择" style="width: 100%;">
  455. <el-option
  456. v-for="item in invoiceTypeList"
  457. :key="item.id"
  458. :label="item.invoiceTypeName"
  459. :value="item.id"
  460. />
  461. </el-select>
  462. </el-form-item>
  463. </el-col>
  464. <el-col :span="12">
  465. <el-form-item label="合同金额:" prop="contractAmount">
  466. <el-input v-model="contractForm.contractAmount" placeholder="请输入">
  467. <template #append>万元</template>
  468. </el-input>
  469. </el-form-item>
  470. </el-col>
  471. </el-row>
  472. <el-row :gutter="20">
  473. <el-col :span="12">
  474. <el-form-item label="税率:" prop="taxRate">
  475. <el-select v-model="contractForm.taxRate" placeholder="请选择" style="width: 100%;">
  476. <el-option
  477. v-for="item in taxRateList"
  478. :key="item.id"
  479. :label="item.taxrateName"
  480. :value="item.id"
  481. />
  482. </el-select>
  483. </el-form-item>
  484. </el-col>
  485. <el-col :span="12">
  486. <el-form-item label="结算方式:" prop="settlementMethod">
  487. <el-select v-model="contractForm.settlementMethod" placeholder="请选择" style="width: 100%;">
  488. <el-option
  489. v-for="item in settlementMethodList"
  490. :key="item.id"
  491. :label="item.settlementName"
  492. :value="item.id"
  493. />
  494. </el-select>
  495. </el-form-item>
  496. </el-col>
  497. </el-row>
  498. <el-row :gutter="20">
  499. <el-col :span="24">
  500. <el-form-item label="合同附件:" prop="contractAttachment">
  501. <FileUpload
  502. v-model="contractForm.contractAttachment"
  503. :limit="10"
  504. :file-size="50"
  505. :file-type="['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'pdf']"
  506. :disabled="contractDialogReadonly"
  507. />
  508. </el-form-item>
  509. </el-col>
  510. </el-row>
  511. <el-row :gutter="20">
  512. <el-col :span="24">
  513. <el-form-item label="合同说明:" prop="contractDescription">
  514. <el-input
  515. v-model="contractForm.contractDescription"
  516. type="textarea"
  517. :rows="4"
  518. placeholder="请输入合同说明"
  519. />
  520. </el-form-item>
  521. </el-col>
  522. </el-row>
  523. </el-form>
  524. <template #footer>
  525. <div class="dialog-footer">
  526. <el-button @click="contractDialogVisible = false">取消</el-button>
  527. <el-button v-if="!contractDialogReadonly" type="primary" @click="handleContractSubmit" :loading="contractSubmitLoading">确定</el-button>
  528. </div>
  529. </template>
  530. </el-dialog>
  531. <!-- 品牌新增对话框 -->
  532. <el-dialog
  533. v-model="brandDialogVisible"
  534. title="添加品牌"
  535. width="600px"
  536. :close-on-click-modal="false"
  537. class="brand-dialog"
  538. >
  539. <div class="brand-dialog__search">
  540. <el-form @submit.prevent>
  541. <el-row :gutter="12" align="middle">
  542. <el-col :span="14">
  543. <el-form-item label="品牌名称:" style="margin-bottom: 0;">
  544. <el-input
  545. v-model="brandSearchKeyword"
  546. placeholder="请输入品牌名称"
  547. @keyup.enter="handleSearchBrand"
  548. />
  549. </el-form-item>
  550. </el-col>
  551. <el-col :span="10" style="display: flex; justify-content: flex-end;">
  552. <el-form-item label-width="0" style="margin-bottom: 0;">
  553. <el-button type="primary" @click="handleSearchBrand" :loading="brandSearchLoading">搜索</el-button>
  554. <el-button type="primary" @click="handleAddBrandToList()">添加</el-button>
  555. </el-form-item>
  556. </el-col>
  557. </el-row>
  558. </el-form>
  559. </div>
  560. <!-- 搜索结果列表 -->
  561. <div v-if="brandSearchResults.length > 0" class="brand-search-results">
  562. <div class="search-results-title">搜索结果(点击添加):</div>
  563. <div class="brand-result-item"
  564. v-for="brand in brandSearchResults"
  565. :key="brand.id"
  566. @click="handleAddBrandToList(brand)"
  567. style="cursor: pointer;"
  568. >
  569. <span class="brand-result-name">{{ brand.brandName }}</span>
  570. <span class="brand-result-no">编号: {{ brand.brandNo }}</span>
  571. </div>
  572. </div>
  573. <div class="selected-brands-section">
  574. <div class="section-label">已选择品牌:</div>
  575. <div class="brand-tags-container">
  576. <el-tag
  577. v-for="brand in tempSelectedBrands"
  578. :key="brand.id"
  579. closable
  580. @close="handleRemoveTempBrand(brand)"
  581. type="info"
  582. style="margin-right: 10px; margin-bottom: 10px;"
  583. >
  584. {{ brand.brandName }}
  585. </el-tag>
  586. <span v-if="tempSelectedBrands.length === 0" style="color: #999; font-size: 14px;">暂无已选择品牌</span>
  587. </div>
  588. </div>
  589. <template #footer>
  590. <div class="dialog-footer">
  591. <el-button @click="handleBrandDialogClose">取消</el-button>
  592. <el-button type="primary" @click="handleBrandSubmit" :loading="brandSubmitLoading">保存</el-button>
  593. </div>
  594. </template>
  595. </el-dialog>
  596. </div>
  597. </template>
  598. <script setup lang="ts">
  599. import { ref, onMounted } from 'vue';
  600. import { useRoute, useRouter } from 'vue-router';
  601. import { ArrowLeft, Plus, Search } from '@element-plus/icons-vue';
  602. import { ElMessage, ElMessageBox } from 'element-plus';
  603. import { useUserStore } from '@/store/modules/user';
  604. import RegionCascader from '@/components/RegionCascader/index.vue';
  605. import FileUpload from '@/components/FileUpload/index.vue';
  606. import Pagination from '@/components/Pagination/index.vue';
  607. import { getInfo, addInfo, updateInfo, scmEditInfo, getStaffListSplice, getSupplierStaffIds, getContactListById, getSupplierCategories, getSupplierContractsById, getBankBySupplierId, getAuthorizeDetailList, savePurchaseInfo, getDictData, getTaxRateList, getSettlementMethodList, getInvoiceTypeList, listInfo } from '@/api/customer/info';
  608. import { getProductCategoryList } from '@/api/product/category';
  609. import { getBank, updateBank, addBank } from '@/api/customer/bank';
  610. import { BankForm } from '@/api/customer/bank/types';
  611. import { getContact, addContact, updateContact } from '@/api/customer/contact';
  612. import { ContactForm } from '@/api/customer/contact/types';
  613. import { InfoVO } from '@/api/customer/info/types';
  614. import { listCompany } from '@/api/company/company';
  615. import { CompanyVO } from '@/api/company/company/types';
  616. import { listEnterpriseScale } from '@/api/customer/customerCategory/enterpriseScale';
  617. import { EnterpriseScaleVO } from '@/api/customer/customerCategory/enterpriseScale/types';
  618. import { listIndustryCategory } from '@/api/customer/customerCategory/industryCategory';
  619. import { IndustryCategoryVO } from '@/api/customer/customerCategory/industryCategory/types';
  620. import { listLevel } from '@/api/system/level';
  621. import { LevelVO } from '@/api/system/level/types';
  622. import { listType } from '@/api/system/type';
  623. import { TypeVO } from '@/api/system/type/types';
  624. import { listBrand, getBrand } from '@/api/product/brand';
  625. import { BrandVO } from '@/api/product/brand/types';
  626. import { listAddress, getAddress, addAddress, updateAddress, delAddress } from '@/api/supplier/address';
  627. import { AddressForm } from '@/api/supplier/address/types';
  628. import { addArea, listArea, getArea } from '@/api/supplier/area';
  629. import { listContract, getContract, addContract, updateContract } from '@/api/supplier/contract';
  630. import { ContractForm } from '@/api/supplier/contract/types';
  631. import { getBusinessInformation } from '@/api/customer/bussineInfo/index';
  632. import { listInvoiceType } from '@/api/system/invoiceType';
  633. import { InvoiceTypeVO } from '@/api/system/invoiceType/types';
  634. import { listBank as listSystemBank } from '@/api/system/bank';
  635. import { BankVO as SystemBankVO } from '@/api/system/bank/types';
  636. import { getInfoTemporary } from '@/api/supplier/infoTemporary';
  637. import { getChinaArea } from '@/api/system/addressarea/index';
  638. import download from '@/plugins/download';
  639. import BasicInfoTab from './components/BasicInfoTab.vue';
  640. import PurchaseInfoTab from './components/PurchaseInfoTab.vue';
  641. import ContactTab from './components/ContactTab.vue';
  642. import SupplyInfoTab from './components/SupplyInfoTab.vue';
  643. import AddressTab from './components/AddressTab.vue';
  644. import ContractTab from './components/ContractTab.vue';
  645. const route = useRoute();
  646. const router = useRouter();
  647. const userStore = useUserStore();
  648. const activeTab = ref('basic');
  649. const isAddMode = ref(false); // 是否为新增模式
  650. const isEditMode = ref(false); // 是否为编辑模式
  651. const isViewMode = ref(false); // 是否为查看模式
  652. const isBasicInfoSaved = ref(false); // 基础信息是否已保存(仅用于新增模式)
  653. const detailData = ref<InfoVO>({} as InfoVO);
  654. const staffOptions = ref<any[]>([]);
  655. const selectedProductManager = ref<number | null>(null);
  656. const selectedBuyer = ref<number | null>(null);
  657. const companyOptions = ref<CompanyVO[]>([]);
  658. const enterpriseScaleOptions = ref<EnterpriseScaleVO[]>([]);
  659. const industryCategoryOptions = ref<IndustryCategoryVO[]>([]);
  660. const supplierLevelOptions = ref<LevelVO[]>([]);
  661. const supplierTypeOptions = ref<TypeVO[]>([]);
  662. // 工商信息对象
  663. const businessInfo = ref<any>({});
  664. const businessInfoLoading = ref(false); // 获取工商信息的loading状态
  665. const businessInfoCache = new Map<string, any>();
  666. const lastBusinessInfoQuery = ref('');
  667. const lastBusinessInfoQueryAt = ref(0);
  668. const businessInfoInFlight = ref(false);
  669. // 联系人相关数据
  670. const contactList = ref<any[]>([]);
  671. const contactLoading = ref(false);
  672. const contactSearchParams = ref({
  673. userNo: '',
  674. userName: ''
  675. });
  676. // 联系人对话框相关
  677. const contactDialogVisible = ref(false);
  678. const contactDialogTitle = ref('');
  679. const contactDialogReadonly = ref(false);
  680. const contactFormRef = ref<ElFormInstance>();
  681. const contactSubmitLoading = ref(false);
  682. const contactForm = ref<ContactForm>({
  683. supplierNo: '',
  684. supplierId: undefined,
  685. userNo: '',
  686. userName: '',
  687. phone: '',
  688. roleNo: '',
  689. departmentNo: '',
  690. position: '',
  691. isPrimaryContact: '0',
  692. isRegister: '0',
  693. email: '',
  694. fax: '',
  695. remark: ''
  696. });
  697. const onContactPhoneInput = (val: string) => {
  698. const next = (val || '').replace(/\D+/g, '').slice(0, 11);
  699. if (next !== contactForm.value.phone) {
  700. contactForm.value.phone = next;
  701. }
  702. };
  703. // 联系人表单验证规则
  704. const contactFormRules = {
  705. userName: [{ required: true, message: '请输入员工姓名', trigger: 'blur' }],
  706. phone: [
  707. { required: true, message: '请输入手机号', trigger: 'blur' },
  708. { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的11位手机号', trigger: 'blur' }
  709. ]
  710. };
  711. // 供应信息相关数据
  712. const productCategoryList = ref<any[]>([]);
  713. const selectedCategories = ref<string[]>([]);
  714. const supplyAreaList = ref<any[]>([]);
  715. // 授权详情信息列表(静态数据)
  716. const authorizationList = ref<any[]>([]);
  717. const authorizationPagination = ref({
  718. pageNum: 1,
  719. pageSize: 10,
  720. total: 0
  721. });
  722. // 供货区域对话框相关
  723. const supplyAreaDialogVisible = ref(false);
  724. const supplyAreaSubmitLoading = ref(false);
  725. const selectedSupplyAreaCodes = ref<string[]>([]); // 选中的供货区域(省/市ID数组)
  726. const savedAreaData = ref<any[]>([]); // 已保存的供货区域数据
  727. // 供货区域选择器数据源(从接口获取)
  728. const supplyAreaOptions = ref<any[]>([]);
  729. // 详细地址级联选择器相关
  730. const regionOptions = ref<any[]>([]); // 省市区级联选择器选项
  731. const selectedOfficeRegion = ref<string[]>([]); // 选中的省市区代码数组
  732. // 品牌相关数据
  733. const selectedBrands = ref<BrandVO[]>([]); // 已选择的品牌列表
  734. const brandDialogVisible = ref(false);
  735. const brandSearchKeyword = ref(''); // 品牌搜索关键词
  736. const tempSelectedBrands = ref<BrandVO[]>([]); // 弹框中临时选择的品牌
  737. const brandSearchResults = ref<BrandVO[]>([]); // 品牌搜索结果
  738. const allBrandList = ref<BrandVO[]>([]); // 所有品牌列表
  739. const brandSubmitLoading = ref(false);
  740. const brandSearchLoading = ref(false);
  741. /** 获取授权详情列表 */
  742. const getAuthorizationList = async () => {
  743. let id = route.query.id as string;
  744. // 如果URL中没有id,暂时跳过
  745. if (!id) {
  746. console.warn('缺少供应商ID,无法获取授权详情列表');
  747. return;
  748. }
  749. try {
  750. const res = await getAuthorizeDetailList({
  751. supplierId: id,
  752. pageNum: authorizationPagination.value.pageNum,
  753. pageSize: authorizationPagination.value.pageSize
  754. });
  755. console.log('授权详情API返回:', res);
  756. authorizationList.value = res.rows || res.data || [];
  757. authorizationPagination.value.total = res.total || 0;
  758. console.log('授权详情列表:', authorizationList.value);
  759. console.log('分页信息:', authorizationPagination.value);
  760. } catch (e) {
  761. console.error('获取授权详情列表失败:', e);
  762. }
  763. };
  764. // 地址管理列表
  765. const addressList = ref<any[]>([]);
  766. // 地址管理对话框相关
  767. const addressDialogVisible = ref(false);
  768. const addressDialogTitle = ref('');
  769. const addressDialogReadonly = ref(false);
  770. const addressFormRef = ref<ElFormInstance>();
  771. const addressSubmitLoading = ref(false);
  772. const selectedAddressRegion = ref<string[]>([]); // 地址的省市区代码数组
  773. const addressForm = ref<AddressForm>({
  774. supplierNo: '',
  775. supplierId: undefined,
  776. shipperName: '',
  777. shipperPhone: '',
  778. shippingPostCode: '',
  779. shippingProvincial: '',
  780. shippingCity: '',
  781. shippingCounty: '',
  782. shippingAddress: '',
  783. isSelf: 0
  784. });
  785. // 地址表单验证规则
  786. const addressFormRules = {
  787. shipperName: [{ required: true, message: '请输入收货人', trigger: 'blur' }],
  788. shipperPhone: [{ required: true, message: '请输入手机号码', trigger: 'blur' }],
  789. shippingProvincial: [{ required: true, message: '请选择地址', trigger: 'change' }],
  790. shippingAddress: [{ required: true, message: '请输入详细地址', trigger: 'blur' }]
  791. };
  792. // 付款信息列表
  793. const paymentInfoList = ref<any[]>([]);
  794. // 付款信息对话框相关
  795. const paymentDialogVisible = ref(false);
  796. const paymentDialogTitle = ref('');
  797. const paymentDialogReadonly = ref(false);
  798. const paymentFormRef = ref<ElFormInstance>();
  799. const paymentSubmitLoading = ref(false);
  800. const paymentForm = ref<BankForm>({
  801. num: undefined,
  802. supplierNo: '',
  803. bankNum: '',
  804. bankInfoNo: undefined,
  805. bankName: '',
  806. bankNo: '',
  807. isture: '1',
  808. circlesName: '',
  809. phone: '',
  810. invoiceTypeNo: undefined,
  811. invoiceTypeName: '',
  812. businessName: '',
  813. businessAddress: ''
  814. });
  815. const fixedPhoneReg = /^(\d{3,4}-?)?\d{7,8}(-\d{1,6})?$/;
  816. // 发票类型列表和银行列表
  817. const invoiceTypeList = ref<InvoiceTypeVO[]>([]);
  818. const systemBankList = ref<SystemBankVO[]>([]);
  819. // 付款信息表单验证规则
  820. const paymentFormRules = {
  821. invoiceTypeNo: [{ required: true, message: '请选择开票类型', trigger: 'change' }],
  822. bankNum: [
  823. {
  824. required: true,
  825. validator: (_rule: any, value: any, callback: any) => {
  826. const v = String(value ?? '').trim();
  827. if (!v) return callback(new Error('请输入开户行行号'));
  828. if (!/^\d+$/.test(v)) return callback(new Error('开户行行号只能输入数字'));
  829. callback();
  830. },
  831. trigger: 'blur'
  832. }
  833. ],
  834. bankInfoNo: [{ required: true, message: '请选择开户行名称', trigger: 'change' }],
  835. bankNo: [
  836. {
  837. required: true,
  838. validator: (_rule: any, value: any, callback: any) => {
  839. const v = String(value ?? '').trim();
  840. if (!v) return callback(new Error('请输入银行账户'));
  841. if (!/^\d+$/.test(v)) return callback(new Error('银行账户只能输入数字'));
  842. callback();
  843. },
  844. trigger: 'blur'
  845. }
  846. ],
  847. phone: [
  848. {
  849. required: true,
  850. validator: (_rule: any, value: any, callback: any) => {
  851. const v = String(value ?? '').trim();
  852. if (!v) return callback(new Error('请输入固定电话'));
  853. if (!fixedPhoneReg.test(v)) return callback(new Error('请输入正确的固定电话'));
  854. callback();
  855. },
  856. trigger: 'blur'
  857. }
  858. ],
  859. num: [{ required: true, message: '请选择是否主账号', trigger: 'change' }]
  860. };
  861. /** 获取发票类型列表 */
  862. const getInvoiceTypeData = async () => {
  863. try {
  864. const res = await listInvoiceType({ pageNum: 1, pageSize: 1000 });
  865. invoiceTypeList.value = res.rows || [];
  866. } catch (e) {
  867. console.error('获取发票类型失败:', e);
  868. }
  869. };
  870. /** 获取银行列表 */
  871. const getSystemBankData = async () => {
  872. try {
  873. const res = await listSystemBank({ pageNum: 1, pageSize: 1000 });
  874. systemBankList.value = res.rows || [];
  875. } catch (e) {
  876. console.error('获取银行列表失败:', e);
  877. }
  878. };
  879. /** 获取付款信息 */
  880. const getPaymentInfo = async () => {
  881. const id = route.query.id as string;
  882. if (!id) return;
  883. try {
  884. const res = await getBankBySupplierId(id);
  885. // 后端返回数组
  886. if (res.data) {
  887. paymentInfoList.value = Array.isArray(res.data) ? res.data : [res.data];
  888. }
  889. } catch (e) {
  890. console.error('获取付款信息失败:', e);
  891. }
  892. };
  893. // 合同管理相关数据
  894. const contractList = ref<any[]>([]);
  895. const contractSearchParams = ref({
  896. contractNo: '',
  897. contractName: '',
  898. contractType: '',
  899. contractStartTime: '' as string | Date | '',
  900. contractEndTime: '' as string | Date | '',
  901. contractStatus: ''
  902. });
  903. const contractPagination = ref({
  904. pageNum: 1,
  905. pageSize: 10,
  906. total: 0
  907. });
  908. const contractStatusDict = ref<any[]>([
  909. { dictLabel: '待审核', dictValue: '0' },
  910. { dictLabel: '生效', dictValue: '1' },
  911. { dictLabel: '失效', dictValue: '2' }
  912. ]);
  913. // 合同对话框相关
  914. const contractDialogVisible = ref(false);
  915. const contractDialogTitle = ref('');
  916. const contractDialogReadonly = ref(false);
  917. const contractFormRef = ref<ElFormInstance>();
  918. const contractSubmitLoading = ref(false);
  919. const contractTypeDict = ref<any[]>([]); // 合同类型字典
  920. const taxRateList = ref<any[]>([]); // 税率列表
  921. const settlementMethodList = ref<any[]>([]); // 结算方式列表
  922. const contractForm = ref<ContractForm>({
  923. supplierNo: '',
  924. supplierId: undefined,
  925. contractNo: '',
  926. contractName: '',
  927. contractType: '',
  928. contractStartTime: '',
  929. contractEndTime: '',
  930. contractStatus: 0,
  931. demandReminderTime: 4,
  932. taxRate: undefined,
  933. contractAmount: undefined,
  934. contractDescription: '',
  935. contractAttachment: '',
  936. settlementMethod: '',
  937. invoiceType: ''
  938. });
  939. // 合同表单验证规则
  940. const contractFormRules = {
  941. contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }],
  942. contractType: [{ required: true, message: '请选择合同类型', trigger: 'change' }],
  943. contractStartTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
  944. contractEndTime: [{ required: true, message: '请选择截止时间', trigger: 'change' }]
  945. };
  946. /** 返回列表页 */
  947. const goBack = () => {
  948. // 使用浏览器后退,返回上一页
  949. router.back();
  950. };
  951. /** 获取工商信息 */
  952. const handleGetBusinessInfo = async () => {
  953. const queryName = (detailData.value.businessName || '').trim();
  954. if (!queryName) {
  955. ElMessage.warning('请先输入工商名称');
  956. return;
  957. }
  958. const now = Date.now();
  959. if (businessInfoInFlight.value && lastBusinessInfoQuery.value === queryName) {
  960. return;
  961. }
  962. if (lastBusinessInfoQuery.value === queryName && now - lastBusinessInfoQueryAt.value < 1500) {
  963. return;
  964. }
  965. if (businessInfoCache.has(queryName)) {
  966. const cached = businessInfoCache.get(queryName);
  967. businessInfo.value = cached;
  968. if (cached?.businessName) detailData.value.businessName = cached.businessName;
  969. if (cached?.socialCreditCode) detailData.value.socialCreditCode = cached.socialCreditCode;
  970. if (cached?.legalPersonName) detailData.value.legalPersonName = cached.legalPersonName;
  971. if (cached?.registeredCapital) detailData.value.registeredCapital = cached.registeredCapital;
  972. return;
  973. }
  974. try {
  975. businessInfoLoading.value = true;
  976. businessInfoInFlight.value = true;
  977. lastBusinessInfoQuery.value = queryName;
  978. lastBusinessInfoQueryAt.value = now;
  979. // 调用获取工商信息接口
  980. const res = await getBusinessInformation(queryName);
  981. if (res.code === 200 && res.data) {
  982. // 将获取到的工商信息填充到表单
  983. businessInfo.value = res.data;
  984. businessInfoCache.set(queryName, res.data);
  985. // 自动填充基本信息表单的相关字段
  986. if (res.data.businessName) {
  987. detailData.value.businessName = res.data.businessName;
  988. }
  989. if (res.data.socialCreditCode) {
  990. detailData.value.socialCreditCode = res.data.socialCreditCode;
  991. }
  992. if (res.data.legalPersonName) {
  993. detailData.value.legalPersonName = res.data.legalPersonName;
  994. }
  995. if (res.data.registeredCapital) {
  996. detailData.value.registeredCapital = res.data.registeredCapital;
  997. }
  998. ElMessage.success('工商信息获取成功');
  999. } else {
  1000. ElMessage.error(res.msg || '获取工商信息失败');
  1001. }
  1002. } catch (e) {
  1003. console.error('获取工商信息失败:', e);
  1004. ElMessage.error('获取工商信息失败');
  1005. } finally {
  1006. businessInfoLoading.value = false;
  1007. businessInfoInFlight.value = false;
  1008. }
  1009. };
  1010. /** 保存数据 */
  1011. const handleSave = async () => {
  1012. try {
  1013. // 验证必填字段
  1014. if (!detailData.value.ownedCompany) {
  1015. ElMessage.warning('请选择所属公司');
  1016. return;
  1017. }
  1018. if (!detailData.value.enterpriseName) {
  1019. ElMessage.warning('请输入企业名称');
  1020. return;
  1021. }
  1022. if (!detailData.value.businessName) {
  1023. ElMessage.warning('请输入工商名称');
  1024. return;
  1025. }
  1026. if (detailData.value.fixedPhone && !fixedPhoneReg.test(String(detailData.value.fixedPhone).trim())) {
  1027. ElMessage.warning('请输入正确的固定电话');
  1028. return;
  1029. }
  1030. if (!detailData.value.shortName) {
  1031. ElMessage.warning('请输入企业简称');
  1032. return;
  1033. }
  1034. if (!detailData.value.cooperateLevel) {
  1035. ElMessage.warning('请选择供应商等级');
  1036. return;
  1037. }
  1038. if (!detailData.value.membershipSize) {
  1039. ElMessage.warning('请选择企业规模');
  1040. return;
  1041. }
  1042. if (!detailData.value.industrCategory) {
  1043. ElMessage.warning('请选择行业类别');
  1044. return;
  1045. }
  1046. if (!detailData.value.supplierType) {
  1047. ElMessage.warning('请选择供应商类型');
  1048. return;
  1049. }
  1050. if (!detailData.value.officeProvince) {
  1051. ElMessage.warning('请选择详细地址省份');
  1052. return;
  1053. }
  1054. // 准备提交数据,包含工商信息
  1055. const submitData: any = {
  1056. ...detailData.value,
  1057. supplierType: String(detailData.value.supplierType), // 确保是字符串类型
  1058. cooperateLevel: String(detailData.value.cooperateLevel), // 确保是字符串类型
  1059. // 将工商信息对象转换为 JSON 字符串存储到 otherCustomers 字段
  1060. otherCustomers: businessInfo.value && Object.keys(businessInfo.value).length > 0
  1061. ? JSON.stringify(businessInfo.value)
  1062. : detailData.value.otherCustomers
  1063. };
  1064. // 根据模式调用新增或更新接口
  1065. let res;
  1066. if (isAddMode.value && !isBasicInfoSaved.value) {
  1067. // 新增模式,调用新增接口
  1068. res = await addInfo(submitData);
  1069. } else {
  1070. // 编辑模式,调用SCM更新接口(不走审核)
  1071. res = await scmEditInfo(submitData);
  1072. }
  1073. ElMessage.success('保存成功');
  1074. // 如果是新增模式,保存成功后标记为已保存,启用其他标签页
  1075. if (isAddMode.value && !isBasicInfoSaved.value) {
  1076. // 获取新创建的供应商ID
  1077. let newId = null;
  1078. if (res.data && res.data.id) {
  1079. newId = res.data.id;
  1080. } else {
  1081. // 如果后端没返回ID,通过企业名称查询获取ID
  1082. try {
  1083. const listRes = await listInfo({
  1084. enterpriseName: detailData.value.enterpriseName,
  1085. pageNum: 1,
  1086. pageSize: 1
  1087. });
  1088. if (listRes.rows && listRes.rows.length > 0) {
  1089. newId = listRes.rows[0].id;
  1090. }
  1091. } catch (e) {
  1092. console.error('获取新创建的供应商ID失败:', e);
  1093. }
  1094. }
  1095. // 更新路由参数(保持 mode=add,添加 id)
  1096. if (newId) {
  1097. await router.replace({
  1098. path: route.path,
  1099. query: { id: newId, mode: 'add' }
  1100. });
  1101. // 标记基础信息已保存,启用其他标签页
  1102. isBasicInfoSaved.value = true;
  1103. detailData.value.id = newId;
  1104. // 重新加载供应商详情以获取完整信息(包括supplierNo)
  1105. try {
  1106. const detailRes = await getInfo(newId);
  1107. detailData.value = detailRes.data;
  1108. } catch (e) {
  1109. console.error('加载供应商详情失败:', e);
  1110. }
  1111. // 加载其他标签页数据
  1112. getContactList();
  1113. getProductCategories();
  1114. getContractList();
  1115. getPaymentInfo();
  1116. getAuthorizationList();
  1117. getAddressList();
  1118. getSupplyAreaList();
  1119. } else {
  1120. ElMessage.warning('无法获取新创建的供应商ID');
  1121. }
  1122. } else {
  1123. // 编辑模式,直接刷新当前数据
  1124. if (detailData.value.id) {
  1125. await getDetail();
  1126. }
  1127. }
  1128. } catch (e) {
  1129. console.error('保存失败:', e);
  1130. }
  1131. };
  1132. /** 初始化省市区级联选择器数据 */
  1133. const initRegionOptions = async () => {
  1134. try {
  1135. const { regionData } = await import('element-china-area-data');
  1136. regionOptions.value = regionData as any[];
  1137. } catch (e) {
  1138. console.error('初始化省市区数据失败:', e);
  1139. }
  1140. };
  1141. /** 初始化供货区域选项数据 */
  1142. const initSupplyAreaOptions = async () => {
  1143. try {
  1144. const res = await getChinaArea();
  1145. const raw = (res as any)?.data || (res as any)?.rows || res || [];
  1146. const mapTree = (nodes: any[]): any[] => {
  1147. return (nodes || []).map((n: any) => ({
  1148. label: n.areaName,
  1149. value: String(n.id),
  1150. areaCode: String(n.areaCode ?? ''),
  1151. parentId: n.parentCode ?? n.parentId,
  1152. level: n.level,
  1153. children: n.children && n.children.length ? mapTree(n.children) : []
  1154. }));
  1155. };
  1156. supplyAreaOptions.value = mapTree(raw);
  1157. } catch (e) {
  1158. console.error('初始化供货区域选项失败:', e);
  1159. supplyAreaOptions.value = [];
  1160. }
  1161. };
  1162. /** 详细地址省市区改变事件 */
  1163. const handleOfficeRegionChange = (value: string[]) => {
  1164. if (value && value.length === 3) {
  1165. // value 是 [省代码, 市代码, 区代码]
  1166. const [provinceCode, cityCode, districtCode] = value;
  1167. // 同时保存 code(后端若支持可直接入库,不支持则忽略;前端回显优先使用 code)
  1168. (detailData.value as any).officeProvinceCode = provinceCode;
  1169. (detailData.value as any).officeCityCode = cityCode;
  1170. (detailData.value as any).officeCountyCode = districtCode;
  1171. // 从 regionOptions 中查找对应的名称
  1172. let provinceName = '';
  1173. let cityName = '';
  1174. let districtName = '';
  1175. const province = regionOptions.value.find((p: any) => p.value === provinceCode);
  1176. if (province) {
  1177. provinceName = province.label;
  1178. const city = province.children?.find((c: any) => c.value === cityCode);
  1179. if (city) {
  1180. cityName = city.label;
  1181. const district = city.children?.find((d: any) => d.value === districtCode);
  1182. if (district) {
  1183. districtName = district.label;
  1184. }
  1185. }
  1186. }
  1187. // 更新 detailData
  1188. detailData.value.officeProvince = provinceName;
  1189. detailData.value.officeCity = cityName;
  1190. detailData.value.officeCounty = districtName;
  1191. } else {
  1192. // 清空
  1193. (detailData.value as any).officeProvinceCode = '';
  1194. (detailData.value as any).officeCityCode = '';
  1195. (detailData.value as any).officeCountyCode = '';
  1196. detailData.value.officeProvince = '';
  1197. detailData.value.officeCity = '';
  1198. detailData.value.officeCounty = '';
  1199. }
  1200. };
  1201. /** 获取详情数据 */
  1202. const getDetail = async () => {
  1203. const id = route.query.id as string;
  1204. const mode = route.query.mode as string;
  1205. // 重置所有模式标志
  1206. isAddMode.value = false;
  1207. isEditMode.value = false;
  1208. isViewMode.value = false;
  1209. isBasicInfoSaved.value = false;
  1210. // 判断模式
  1211. if (mode === 'add') {
  1212. // 新增模式
  1213. isAddMode.value = true;
  1214. if (id) {
  1215. // 如果有ID,说明基础信息已保存
  1216. isBasicInfoSaved.value = true;
  1217. }
  1218. return;
  1219. } else if (mode === 'edit' && id) {
  1220. // 编辑模式(必须有ID)
  1221. isEditMode.value = true;
  1222. } else if (id) {
  1223. // 查看模式(有ID但没有mode或mode=view)
  1224. isViewMode.value = true;
  1225. } else {
  1226. // 没有ID也没有mode,默认为新增模式
  1227. isAddMode.value = true;
  1228. return;
  1229. }
  1230. // 有ID的情况下,加载详情数据
  1231. if (id) {
  1232. const res = await getInfo(id);
  1233. detailData.value = res.data;
  1234. // 如果是edit模式且供应商状态为"待修改审核"(4),需要查询临时表数据
  1235. if (isEditMode.value && detailData.value.supplyStatus === '4') {
  1236. try {
  1237. const tempRes = await getInfoTemporary(id);
  1238. if (tempRes.data) {
  1239. // 使用临时表数据覆盖主表数据
  1240. const tempData: any = tempRes.data;
  1241. detailData.value = {
  1242. ...detailData.value, // 保留主表的一些基础字段
  1243. ...tempData, // 使用临时表的修改数据
  1244. id: detailData.value.id, // 确保ID是主表的ID
  1245. supplierNo: detailData.value.supplierNo // 确保supplierNo是主表的
  1246. } as any;
  1247. console.log('已加载临时表数据:', tempData);
  1248. }
  1249. } catch (e) {
  1250. console.error('查询临时表失败:', e);
  1251. // 查询失败,继续使用主表数据
  1252. }
  1253. }
  1254. // 如果 personImageUrl 为空但 personImage 有值,使用 personImage 的值
  1255. if (!detailData.value.personImageUrl && detailData.value.personImage) {
  1256. detailData.value.personImageUrl = detailData.value.personImage;
  1257. }
  1258. // 初始化品牌列表
  1259. initBrandList();
  1260. // 供货区域列表会在 onMounted 中通过 getSupplyAreaList() 获取,这里不需要初始化
  1261. // 注释掉简单的初始化,避免与 getSupplyAreaList 冲突
  1262. // if ((detailData.value as any).province || (detailData.value as any).city) {
  1263. // supplyAreaList.value = [{
  1264. // province: (detailData.value as any).province || '',
  1265. // city: (detailData.value as any).city || ''
  1266. // }];
  1267. // }
  1268. // 初始化详细地址显示:优先使用已保存的 code;没有 code 时再用 name 反查 code
  1269. const officeProvinceCode = (detailData.value as any).officeProvinceCode;
  1270. const officeCityCode = (detailData.value as any).officeCityCode;
  1271. const officeCountyCode = (detailData.value as any).officeCountyCode;
  1272. if (officeProvinceCode && officeCityCode && officeCountyCode) {
  1273. selectedOfficeRegion.value = [String(officeProvinceCode), String(officeCityCode), String(officeCountyCode)];
  1274. } else if (detailData.value.officeProvince && detailData.value.officeCity && detailData.value.officeCounty) {
  1275. const province = detailData.value.officeProvince;
  1276. const city = detailData.value.officeCity;
  1277. const county = detailData.value.officeCounty;
  1278. // 根据省市区名称查找对应的代码
  1279. const provinceItem = regionOptions.value.find((p: any) => p.label === province);
  1280. if (provinceItem) {
  1281. const cityItem = provinceItem.children?.find((c: any) => c.label === city);
  1282. if (cityItem) {
  1283. const districtItem = cityItem.children?.find((d: any) => d.label === county);
  1284. if (districtItem) {
  1285. selectedOfficeRegion.value = [provinceItem.value, cityItem.value, districtItem.value];
  1286. }
  1287. }
  1288. }
  1289. }
  1290. // 解析工商信息,支持 otherCustomersMap 或 otherCustomers
  1291. const otherCustomersData = (detailData.value as any).otherCustomersMap || detailData.value.otherCustomers;
  1292. if (otherCustomersData) {
  1293. try {
  1294. // 如果是字符串,需要解析JSON
  1295. if (typeof otherCustomersData === 'string') {
  1296. businessInfo.value = JSON.parse(otherCustomersData);
  1297. } else {
  1298. businessInfo.value = otherCustomersData;
  1299. }
  1300. console.log('工商信息:', businessInfo.value);
  1301. } catch (e) {
  1302. console.error('解析工商信息失败:', e);
  1303. }
  1304. }
  1305. // 工商信息字段已经在 detailData 中返回,直接使用
  1306. // 将 detailData 的工商信息字段映射到 businessInfo
  1307. businessInfo.value = {
  1308. businessName: detailData.value.businessName,
  1309. registrationAuthority: (detailData.value as any).registrationAuthority,
  1310. establishmentDate: (detailData.value as any).establishmentDate,
  1311. registrationStatus: (detailData.value as any).registrationStatus,
  1312. paidInCapital: (detailData.value as any).paidInCapital,
  1313. socialCreditCode: detailData.value.socialCreditCode,
  1314. legalPersonName: detailData.value.legalPersonName,
  1315. registeredCapital: detailData.value.registeredCapital,
  1316. revocationDate: (detailData.value as any).revocationDate,
  1317. bussinessRange: (detailData.value as any).bussinessRange,
  1318. businessAddress: (detailData.value as any).businessAddress
  1319. };
  1320. console.log('工商信息(从详情接口):', businessInfo.value);
  1321. // 获取当前供应商的产品经理和采购员ID
  1322. try {
  1323. const staffIdsRes = await getSupplierStaffIds(id);
  1324. const staffIds = staffIdsRes.data; // {productManager: 1, purchaser: 2}
  1325. selectedProductManager.value = staffIds.productManager;
  1326. selectedBuyer.value = staffIds.purchaser;
  1327. console.log('当前产品经理ID:', staffIds.productManager);
  1328. console.log('当前采购员ID:', staffIds.purchaser);
  1329. } catch (e) {
  1330. console.error('获取人员ID失败:', e);
  1331. }
  1332. }
  1333. };
  1334. /** 获取人员下拉选项 */
  1335. const getStaffOptions = async () => {
  1336. try {
  1337. const res = await getStaffListSplice();
  1338. const staffMap = res.data; // Map<Long, String> 格式:{1: "00040,郑春风", 2: "00050,王坤"}
  1339. // 转换为下拉框选项格式
  1340. staffOptions.value = Object.entries(staffMap).map(([staffId, displayText]) => ({
  1341. staffId: Number(staffId),
  1342. displayText: displayText,
  1343. label: displayText,
  1344. value: Number(staffId)
  1345. }));
  1346. } catch (e) {
  1347. console.error('获取人员信息失败:', e);
  1348. }
  1349. };
  1350. /** 获取公司下拉选项 */
  1351. const getCompanyOptions = async () => {
  1352. try {
  1353. const res = await listCompany({
  1354. pageNum: 1,
  1355. pageSize: 1000,
  1356. status: '0'
  1357. }); // 只获取正常状态的公司
  1358. companyOptions.value = res.rows || [];
  1359. } catch (e) {
  1360. console.error('获取公司信息失败:', e);
  1361. }
  1362. };
  1363. /** 获取企业规模下拉选项 */
  1364. const getEnterpriseScaleOptions = async () => {
  1365. try {
  1366. const res = await listEnterpriseScale({
  1367. pageNum: 1,
  1368. pageSize: 1000,
  1369. status: '0' // 只获取正常状态的企业规模
  1370. });
  1371. enterpriseScaleOptions.value = res.data || res.rows || [];
  1372. } catch (e) {
  1373. console.error('获取企业规模信息失败:', e);
  1374. }
  1375. };
  1376. /** 获取行业类别下拉选项 */
  1377. const getIndustryCategoryOptions = async () => {
  1378. try {
  1379. const res = await listIndustryCategory({
  1380. pageNum: 1,
  1381. pageSize: 1000,
  1382. status: '0' // 只获取正常状态的行业类别
  1383. });
  1384. industryCategoryOptions.value = res.data || res.rows || [];
  1385. } catch (e) {
  1386. console.error('获取行业类别信息失败:', e);
  1387. }
  1388. };
  1389. /** 获取供应商等级下拉选项 */
  1390. const getSupplierLevelOptions = async () => {
  1391. try {
  1392. const res = await listLevel({
  1393. pageNum: 1,
  1394. pageSize: 1000
  1395. });
  1396. supplierLevelOptions.value = res.data || res.rows || [];
  1397. } catch (e) {
  1398. console.error('获取供应商等级信息失败:', e);
  1399. }
  1400. };
  1401. /** 获取供应商类型下拉选项 */
  1402. const getSupplierTypeOptions = async () => {
  1403. try {
  1404. const res = await listType({
  1405. pageNum: 1,
  1406. pageSize: 1000
  1407. } as any);
  1408. supplierTypeOptions.value = res.data || res.rows || [];
  1409. } catch (e) {
  1410. console.error('获取供应商类型信息失败:', e);
  1411. }
  1412. };
  1413. /** 获取联系人列表 */
  1414. const getContactList = async () => {
  1415. const id = route.query.id as string;
  1416. if (!id) return;
  1417. contactLoading.value = true;
  1418. try {
  1419. const res = await getContactListById(id, {
  1420. pageNum: 1,
  1421. pageSize: 100,
  1422. ...contactSearchParams.value
  1423. });
  1424. contactList.value = res.rows || [];
  1425. } catch (e) {
  1426. console.error('获取联系人列表失败:', e);
  1427. } finally {
  1428. contactLoading.value = false;
  1429. }
  1430. };
  1431. /** 搜索联系人 */
  1432. const handleContactSearch = () => {
  1433. getContactList();
  1434. };
  1435. /** 重置联系人搜索 */
  1436. const handleContactReset = () => {
  1437. contactSearchParams.value = {
  1438. userNo: '',
  1439. userName: ''
  1440. };
  1441. getContactList();
  1442. };
  1443. /** 新增联系人 */
  1444. const handleAddContact = () => {
  1445. // 重置表单
  1446. contactForm.value = {
  1447. supplierNo: detailData.value.supplierNo,
  1448. supplierId: route.query.id as any,
  1449. userNo: '',
  1450. userName: '',
  1451. phone: '',
  1452. roleNo: '',
  1453. departmentNo: '',
  1454. position: '',
  1455. isPrimaryContact: '0',
  1456. isRegister: '0',
  1457. email: '',
  1458. fax: '',
  1459. remark: ''
  1460. };
  1461. contactDialogTitle.value = '新增联系人';
  1462. contactDialogReadonly.value = false;
  1463. contactDialogVisible.value = true;
  1464. };
  1465. /** 查看联系人 */
  1466. const handleViewContact = async (row: any) => {
  1467. try {
  1468. const res = await getContact(row.id);
  1469. Object.assign(contactForm.value, res.data);
  1470. contactDialogTitle.value = '查看联系人';
  1471. contactDialogReadonly.value = true;
  1472. contactDialogVisible.value = true;
  1473. } catch (e) {
  1474. console.error('获取联系人详情失败:', e);
  1475. ElMessage.error('获取联系人详情失败');
  1476. }
  1477. };
  1478. /** 编辑联系人 */
  1479. const handleEditContact = async (row: any) => {
  1480. try {
  1481. const res = await getContact(row.id);
  1482. Object.assign(contactForm.value, res.data);
  1483. contactDialogTitle.value = '编辑联系人';
  1484. contactDialogReadonly.value = false;
  1485. contactDialogVisible.value = true;
  1486. } catch (e) {
  1487. console.error('获取联系人详情失败:', e);
  1488. ElMessage.error('获取联系人详情失败');
  1489. }
  1490. };
  1491. /** 提交联系人 */
  1492. const handleContactSubmit = async () => {
  1493. if (!contactFormRef.value) return;
  1494. contactFormRef.value.validate(async (valid: boolean) => {
  1495. if (!valid) return;
  1496. try {
  1497. contactSubmitLoading.value = true;
  1498. // 设置供应商编号和ID
  1499. if (!contactForm.value.supplierNo && detailData.value.supplierNo) {
  1500. contactForm.value.supplierNo = detailData.value.supplierNo;
  1501. }
  1502. if (!contactForm.value.supplierId && route.query.id) {
  1503. contactForm.value.supplierId = route.query.id as any;
  1504. }
  1505. if ((contactForm.value as any).id) {
  1506. // 更新
  1507. await updateContact(contactForm.value);
  1508. ElMessage.success('更新成功');
  1509. } else {
  1510. // 新增
  1511. await addContact(contactForm.value);
  1512. ElMessage.success('新增成功');
  1513. }
  1514. contactDialogVisible.value = false;
  1515. // 刷新联系人列表
  1516. await getContactList();
  1517. } catch (e) {
  1518. console.error('保存联系人失败:', e);
  1519. ElMessage.error('保存联系人失败');
  1520. } finally {
  1521. contactSubmitLoading.value = false;
  1522. }
  1523. });
  1524. };
  1525. /** 获取产品分类列表 */
  1526. const getProductCategories = async () => {
  1527. try {
  1528. const res = await getProductCategoryList();
  1529. productCategoryList.value = res.rows || res.data || [];
  1530. console.log('产品分类列表:', productCategoryList.value);
  1531. // 获取分类列表后,再获取已选择的品目
  1532. await getSupplierSelectedCategories();
  1533. } catch (e) {
  1534. console.error('获取产品分类失败:', e);
  1535. }
  1536. };
  1537. /** 获取供应商已选择的品目 */
  1538. const getSupplierSelectedCategories = async () => {
  1539. const id = route.query.id as string;
  1540. if (!id) return;
  1541. try {
  1542. // 在edit模式下传递supplyStatus参数
  1543. const supplyStatus = isEditMode.value ? "4" : undefined;
  1544. const res = await getSupplierCategories(id, supplyStatus);
  1545. // 确保数据类型一致,转换为字符串数组
  1546. selectedCategories.value = (res.data || []).map(String);
  1547. console.log('供应商已选择的品目ID:', selectedCategories.value);
  1548. console.log('产品分类列表:', productCategoryList.value);
  1549. } catch (e) {
  1550. console.error('获取供应商品目失败:', e);
  1551. }
  1552. };
  1553. /** 保存供货类目 */
  1554. const handleSaveCategories = async () => {
  1555. try {
  1556. const id = route.query.id as string;
  1557. if (!id) {
  1558. ElMessage.error('供应商ID不存在');
  1559. return;
  1560. }
  1561. // 方案B:只提交必要字段,避免EditGroup校验因缺字段失败
  1562. await scmEditInfo({
  1563. id,
  1564. operatingCategory: selectedCategories.value.join(',')
  1565. } as any);
  1566. ElMessage.success('保存成功');
  1567. } catch (e) {
  1568. console.error('保存供货类目失败:', e);
  1569. }
  1570. };
  1571. /** 保存采购信息 */
  1572. const handleSavePurchaseInfo = async () => {
  1573. try {
  1574. const id = route.query.id as string;
  1575. if (!id) {
  1576. ElMessage.error('供应商ID不存在');
  1577. return;
  1578. }
  1579. // 调用保存采购信息接口
  1580. await savePurchaseInfo({
  1581. supplierId: id,
  1582. productManager: selectedProductManager.value,
  1583. purchaser: selectedBuyer.value
  1584. });
  1585. ElMessage.success('保存成功');
  1586. } catch (e) {
  1587. console.error('保存采购信息失败:', e);
  1588. }
  1589. };
  1590. /** 获取所有品牌列表 */
  1591. const getAllBrandList = async () => {
  1592. try {
  1593. const res = await listBrand({
  1594. pageNum: 1,
  1595. pageSize: 1000 // 获取所有品牌
  1596. });
  1597. allBrandList.value = res.rows || [];
  1598. console.log('品牌列表:', allBrandList.value);
  1599. } catch (e) {
  1600. console.error('获取品牌列表失败:', e);
  1601. }
  1602. };
  1603. /** 新增品牌 - 打开弹框 */
  1604. const handleAddBrand = () => {
  1605. // 复制当前已选择的品牌到临时列表
  1606. tempSelectedBrands.value = [...selectedBrands.value];
  1607. brandSearchKeyword.value = '';
  1608. // 显示所有品牌作为初始搜索结果
  1609. brandSearchResults.value = [...allBrandList.value];
  1610. brandDialogVisible.value = true;
  1611. };
  1612. /** 搜索品牌 */
  1613. const handleSearchBrand = async () => {
  1614. // 如果搜索关键词为空,显示所有品牌
  1615. if (!brandSearchKeyword.value.trim()) {
  1616. brandSearchResults.value = [...allBrandList.value];
  1617. return;
  1618. }
  1619. try {
  1620. brandSearchLoading.value = true;
  1621. const res = await listBrand({
  1622. brandName: brandSearchKeyword.value.trim(),
  1623. pageNum: 1,
  1624. pageSize: 50
  1625. });
  1626. brandSearchResults.value = res.rows || [];
  1627. if (brandSearchResults.value.length === 0) {
  1628. ElMessage.info('未找到匹配的品牌');
  1629. }
  1630. } catch (e) {
  1631. console.error('搜索品牌失败:', e);
  1632. ElMessage.error('搜索品牌失败');
  1633. } finally {
  1634. brandSearchLoading.value = false;
  1635. }
  1636. };
  1637. /** 添加品牌到列表 */
  1638. const handleAddBrandToList = (brand?: BrandVO) => {
  1639. // 如果没有传入品牌,则使用搜索关键词手动创建
  1640. if (!brand) {
  1641. if (!brandSearchKeyword.value.trim()) {
  1642. ElMessage.warning('请输入品牌名称或先搜索品牌');
  1643. return;
  1644. }
  1645. // 手动创建品牌对象(临时方案)
  1646. brand = {
  1647. id: brandSearchKeyword.value.trim(),
  1648. brandName: brandSearchKeyword.value.trim()
  1649. } as BrandVO;
  1650. }
  1651. // 检查品牌是否已存在
  1652. const exists = tempSelectedBrands.value.some(b => b.id === brand!.id);
  1653. if (exists) {
  1654. ElMessage.warning('该品牌已添加');
  1655. return;
  1656. }
  1657. // 添加品牌到临时列表
  1658. tempSelectedBrands.value.push(brand);
  1659. // 不清空搜索框和搜索结果,允许用户继续选择多个品牌
  1660. // brandSearchKeyword.value = '';
  1661. // brandSearchResults.value = [];
  1662. ElMessage.success(`已添加品牌:${brand.brandName}`);
  1663. };
  1664. /** 删除临时品牌 */
  1665. const handleRemoveTempBrand = (brand: BrandVO) => {
  1666. const index = tempSelectedBrands.value.findIndex(b => b.id === brand.id);
  1667. if (index > -1) {
  1668. tempSelectedBrands.value.splice(index, 1);
  1669. }
  1670. };
  1671. /** 删除已选择的品牌 */
  1672. const handleRemoveBrand = async (brand: BrandVO) => {
  1673. const index = selectedBrands.value.findIndex(b => b.id === brand.id);
  1674. if (index > -1) {
  1675. selectedBrands.value.splice(index, 1);
  1676. // 立即保存到后端
  1677. await saveBrandsToServer();
  1678. }
  1679. };
  1680. /** 品牌对话框关闭 */
  1681. const handleBrandDialogClose = () => {
  1682. brandDialogVisible.value = false;
  1683. brandSearchKeyword.value = '';
  1684. };
  1685. /** 提交品牌 */
  1686. const handleBrandSubmit = async () => {
  1687. try {
  1688. brandSubmitLoading.value = true;
  1689. // 更新主品牌列表
  1690. selectedBrands.value = [...tempSelectedBrands.value];
  1691. // 保存到服务器
  1692. await saveBrandsToServer();
  1693. ElMessage.success('保存成功');
  1694. brandDialogVisible.value = false;
  1695. } catch (e) {
  1696. console.error('保存品牌失败:', e);
  1697. } finally {
  1698. brandSubmitLoading.value = false;
  1699. }
  1700. };
  1701. /** 保存品牌到服务器 */
  1702. const saveBrandsToServer = async () => {
  1703. const id = route.query.id as string;
  1704. if (!id) {
  1705. ElMessage.error('供应商ID不存在');
  1706. return;
  1707. }
  1708. // 将品牌ID用逗号分隔保存
  1709. const brandIds = selectedBrands.value.map(brand => brand.id).join(',');
  1710. // 方案B:只提交必要字段,避免EditGroup校验因缺字段失败
  1711. await scmEditInfo({
  1712. id,
  1713. operatingBrand: brandIds
  1714. } as any);
  1715. };
  1716. /** 初始化品牌列表 - 从后端数据解析 */
  1717. const initBrandList = async () => {
  1718. if (detailData.value.operatingBrand) {
  1719. // 后端返回的是用逗号分隔的品牌ID
  1720. const brandIds = detailData.value.operatingBrand.split(',').filter(id => id.trim());
  1721. if (brandIds.length > 0) {
  1722. try {
  1723. // 根据品牌ID获取品牌详细信息
  1724. const brandPromises = brandIds.map(id => getBrand(id.trim()));
  1725. const brandResults = await Promise.allSettled(brandPromises);
  1726. selectedBrands.value = brandResults
  1727. .filter(result => result.status === 'fulfilled')
  1728. .map(result => (result as PromiseFulfilledResult<any>).value.data)
  1729. .filter(brand => brand); // 过滤掉空值
  1730. } catch (e) {
  1731. console.error('获取品牌详情失败:', e);
  1732. // 降级处理:如果获取品牌详情失败,使用ID作为显示名称
  1733. selectedBrands.value = brandIds.map(id => ({
  1734. id: id.trim(),
  1735. brandName: id.trim()
  1736. } as BrandVO));
  1737. }
  1738. }
  1739. }
  1740. };
  1741. /** 获取供货区域列表 */
  1742. const getSupplyAreaList = async () => {
  1743. const id = route.query.id as string;
  1744. if (!id) return;
  1745. try {
  1746. // 构建查询参数
  1747. const queryParams: any = {
  1748. supplierId: id,
  1749. pageNum: 1,
  1750. pageSize: 1000
  1751. };
  1752. // 只在编辑模式下添加 supplyStatus
  1753. if (isEditMode.value) {
  1754. queryParams.supplyStatus = "4";
  1755. }
  1756. const res = await listArea(queryParams);
  1757. // 保存原始数据,用于编辑时回显
  1758. savedAreaData.value = res.data || res.rows || [];
  1759. // 处理返回的数据,按层级组织
  1760. const areaData = savedAreaData.value;
  1761. // 第一步:先收集所有省份(用 areaId 做父子关联 key)
  1762. const provinceMap: any = {};
  1763. areaData.forEach((area: any) => {
  1764. if (area.level === '1' || area.level === 1) {
  1765. const pId = String(area.areaId ?? area.areaCode);
  1766. provinceMap[pId] = {
  1767. province: area.areaName,
  1768. city: ''
  1769. };
  1770. }
  1771. });
  1772. // 第二步:将城市添加到对应的省份
  1773. areaData.forEach((area: any) => {
  1774. if (area.level === '2' || area.level === 2) {
  1775. const pId = String(area.parentId ?? '0');
  1776. if (provinceMap[pId]) {
  1777. if (provinceMap[pId].city) {
  1778. provinceMap[pId].city += ',' + area.areaName;
  1779. } else {
  1780. provinceMap[pId].city = area.areaName;
  1781. }
  1782. }
  1783. }
  1784. });
  1785. // 转换为数组
  1786. supplyAreaList.value = Object.values(provinceMap).filter((item: any) =>
  1787. item.province || item.city
  1788. );
  1789. console.log('供货区域列表:', supplyAreaList.value);
  1790. console.log('保存的原始数据:', savedAreaData.value);
  1791. } catch (e) {
  1792. console.error('获取供货区域列表失败:', e);
  1793. }
  1794. };
  1795. /** 编辑供货区域 */
  1796. const handleEditSupplyArea = async () => {
  1797. supplyAreaDialogVisible.value = true;
  1798. // 等待对话框打开后再设置回显数据
  1799. await nextTick();
  1800. selectedSupplyAreaCodes.value = buildSupplyAreaCodesFromSavedData(savedAreaData.value);
  1801. };
  1802. /** 根据已保存的省/市数据生成 RegionCascader 需要的省/市ID列表 */
  1803. const buildSupplyAreaCodesFromSavedData = (areaData: any[]) => {
  1804. const codes = new Set<string>();
  1805. if (!areaData || areaData.length === 0) return [];
  1806. areaData.forEach((area: any) => {
  1807. const level = String(area.level);
  1808. const areaId = String(area.areaId ?? area.id ?? '');
  1809. if (!areaId) return;
  1810. if (level === '1' || level === '2') {
  1811. codes.add(areaId);
  1812. }
  1813. });
  1814. return Array.from(codes);
  1815. };
  1816. /** 根据省/市ID提取供货区域(省/市)展示字符串 */
  1817. const extractRegionDataFromSelectedProvinceCityIds = (selectedIds: string[]) => {
  1818. const provinces: any[] = supplyAreaOptions.value || [];
  1819. const provinceNames: string[] = [];
  1820. const cityNames: string[] = [];
  1821. const addedProvinces = new Set<string>();
  1822. const addedCities = new Set<string>();
  1823. selectedIds.forEach((id) => {
  1824. for (const province of provinces) {
  1825. if (String(province.value) === String(id)) {
  1826. if (!addedProvinces.has(String(province.value))) {
  1827. provinceNames.push(province.label);
  1828. addedProvinces.add(String(province.value));
  1829. }
  1830. return;
  1831. }
  1832. const cities = province?.children || [];
  1833. const hitCity = cities.find((c: any) => String(c.value) === String(id));
  1834. if (hitCity) {
  1835. if (!addedProvinces.has(String(province.value))) {
  1836. provinceNames.push(province.label);
  1837. addedProvinces.add(String(province.value));
  1838. }
  1839. if (!addedCities.has(String(hitCity.value))) {
  1840. cityNames.push(hitCity.label);
  1841. addedCities.add(String(hitCity.value));
  1842. }
  1843. return;
  1844. }
  1845. }
  1846. });
  1847. return {
  1848. provinces: provinceNames.join(','),
  1849. cities: cityNames.join(',')
  1850. };
  1851. };
  1852. /** 构建 areaList 数组(提交给后端:省(1)/市(2)) */
  1853. const buildAreaListFromSelectedProvinceCityIds = (selectedIds: string[]) => {
  1854. const provinces: any[] = supplyAreaOptions.value || [];
  1855. const areaList: any[] = [];
  1856. const addedProvinces = new Set<string>();
  1857. const addedCities = new Set<string>();
  1858. selectedIds.forEach((id) => {
  1859. for (const province of provinces) {
  1860. // 省
  1861. if (String(province.value) === String(id)) {
  1862. if (!addedProvinces.has(String(province.value))) {
  1863. areaList.push({
  1864. areaId: Number(province.value),
  1865. areaCode: province.areaCode || province.value,
  1866. areaName: province.label,
  1867. level: 1,
  1868. parentId: 0
  1869. });
  1870. addedProvinces.add(String(province.value));
  1871. }
  1872. return;
  1873. }
  1874. // 市
  1875. const cities = province?.children || [];
  1876. const hitCity = cities.find((c: any) => String(c.value) === String(id));
  1877. if (hitCity) {
  1878. // city 已选时确保 parent province 也存在(即使组件没回传,也兜底)
  1879. if (!addedProvinces.has(String(province.value))) {
  1880. areaList.push({
  1881. areaId: Number(province.value),
  1882. areaCode: province.areaCode || province.value,
  1883. areaName: province.label,
  1884. level: 1,
  1885. parentId: 0
  1886. });
  1887. addedProvinces.add(String(province.value));
  1888. }
  1889. if (!addedCities.has(String(hitCity.value))) {
  1890. areaList.push({
  1891. areaId: Number(hitCity.value),
  1892. areaCode: hitCity.areaCode || hitCity.value,
  1893. areaName: hitCity.label,
  1894. level: 2,
  1895. parentId: Number(province.value)
  1896. });
  1897. addedCities.add(String(hitCity.value));
  1898. }
  1899. return;
  1900. }
  1901. }
  1902. });
  1903. return areaList;
  1904. };
  1905. /** 提交供货区域 */
  1906. const handleSupplyAreaSubmit = async () => {
  1907. try {
  1908. supplyAreaSubmitLoading.value = true;
  1909. const id = route.query.id as string;
  1910. if (!id) {
  1911. ElMessage.error('供应商ID不存在');
  1912. return;
  1913. }
  1914. if (!detailData.value.supplierNo) {
  1915. ElMessage.error('供应商编号不存在,请先保存基础信息');
  1916. return;
  1917. }
  1918. console.log('提交前的选中省/市ID:', selectedSupplyAreaCodes.value);
  1919. // 构建 areaList 数组
  1920. const areaList = buildAreaListFromSelectedProvinceCityIds(selectedSupplyAreaCodes.value);
  1921. console.log('构建的 areaList:', areaList);
  1922. const areaListToSubmit = areaList || [];
  1923. // 从选中的编码中提取区域信息(用于显示)
  1924. const regionData = extractRegionDataFromSelectedProvinceCityIds(selectedSupplyAreaCodes.value);
  1925. console.log('提取的区域数据:', regionData);
  1926. // 构建提交数据
  1927. const submitData: any = {
  1928. supplierId: id,
  1929. supplyNo: detailData.value.supplierNo,
  1930. areaList: areaListToSubmit
  1931. };
  1932. console.log('提交的数据:', submitData);
  1933. await addArea(submitData);
  1934. // 更新本地数据
  1935. (detailData.value as any).province = regionData.provinces;
  1936. (detailData.value as any).city = regionData.cities;
  1937. // edit模式下,保存供货区域后需要同步更新主表信息(保留其他字段的修改)
  1938. if (isEditMode.value) {
  1939. try {
  1940. const tempRes = await getInfoTemporary(id);
  1941. let mainSubmitData: any;
  1942. if (tempRes.data) {
  1943. // 临时表已有记录,使用临时表数据作为基础
  1944. const tempData = tempRes.data;
  1945. mainSubmitData = {
  1946. ...tempData, // 临时表中的数据(保留之前的所有修改)
  1947. ...detailData.value, // 当前页面的数据
  1948. supplierType: String(detailData.value.supplierType),
  1949. cooperateLevel: String(detailData.value.cooperateLevel),
  1950. };
  1951. } else {
  1952. // 临时表没有记录,使用当前数据
  1953. mainSubmitData = {
  1954. ...detailData.value,
  1955. supplierType: String(detailData.value.supplierType),
  1956. cooperateLevel: String(detailData.value.cooperateLevel),
  1957. };
  1958. }
  1959. // 删除后端返回的展示字段
  1960. delete mainSubmitData.supplierTypeName;
  1961. delete mainSubmitData.cooperateLevelName;
  1962. delete mainSubmitData.membershipSizeName;
  1963. delete mainSubmitData.industrCategoryName;
  1964. delete mainSubmitData.productManager;
  1965. delete mainSubmitData.buyer;
  1966. delete mainSubmitData.brandName;
  1967. delete mainSubmitData.province;
  1968. delete mainSubmitData.city;
  1969. // 更新主表信息
  1970. await updateInfo(mainSubmitData);
  1971. } catch (e) {
  1972. console.error('更新主表信息失败:', e);
  1973. }
  1974. }
  1975. ElMessage.success('保存成功');
  1976. supplyAreaDialogVisible.value = false;
  1977. await getSupplyAreaList();
  1978. } catch (e) {
  1979. console.error('保存供货区域失败:', e);
  1980. } finally {
  1981. supplyAreaSubmitLoading.value = false;
  1982. }
  1983. };
  1984. /** 供货区域选择变化 */
  1985. const handleSupplyAreaChange = (value: any) => {
  1986. console.log('供货区域选择变化:', value);
  1987. };
  1988. /** 格式化日期 */
  1989. const formatDate = (date: string | Date) => {
  1990. if (!date) return '-';
  1991. const d = new Date(date);
  1992. return d.toISOString().split('T')[0];
  1993. };
  1994. /** 获取合同状态类型 */
  1995. const getContractStatusType = (status: number) => {
  1996. const statusMap = {
  1997. 0: 'info', // 草稿
  1998. 1: 'success', // 已生效
  1999. 2: 'warning', // 已到期
  2000. 3: 'danger', // 已终止
  2001. 4: 'danger' // 已作废
  2002. };
  2003. return statusMap[status] || 'info';
  2004. };
  2005. /** 获取合同状态文本 */
  2006. const getContractStatusText = (status: number) => {
  2007. const statusMap = {
  2008. 0: '待审核',
  2009. 1: '生效',
  2010. 2: '失效'
  2011. };
  2012. return statusMap[status] || '未知';
  2013. };
  2014. /** 获取合同类型文本 */
  2015. const getContractTypeText = (type: string | number) => {
  2016. const typeMap = {
  2017. '0': '年度合作',
  2018. '1': '项目采购',
  2019. '2': '其他合作',
  2020. };
  2021. return typeMap[type] || '未知';
  2022. };
  2023. /** 获取授权状态文本 */
  2024. const getAuthorizedStatusText = (status: string) => {
  2025. const statusMap = {
  2026. '0': '待审核',
  2027. '1': '已生效',
  2028. '2': '已过期'
  2029. };
  2030. return statusMap[status] || '未知';
  2031. };
  2032. /** 搜索合同 */
  2033. const handleContractSearch = () => {
  2034. console.log('搜索合同:', contractSearchParams.value);
  2035. getContractList();
  2036. };
  2037. /** 重置合同搜索 */
  2038. const handleContractReset = () => {
  2039. contractSearchParams.value = {
  2040. contractNo: '',
  2041. contractName: '',
  2042. contractType: '',
  2043. contractStartTime: '',
  2044. contractEndTime: '',
  2045. contractStatus: ''
  2046. };
  2047. getContractList();
  2048. };
  2049. const wanToYuanString = (value: unknown): string => {
  2050. const raw = String(value ?? '').trim();
  2051. if (!raw) return '';
  2052. const normalized = raw.replace(/,/g, '');
  2053. const match = normalized.match(/^(-?)(\d+)(?:\.(\d+))?$/);
  2054. if (!match) return '';
  2055. const sign = match[1] ? '-' : '';
  2056. const intPart = match[2] || '0';
  2057. const fracRaw = match[3] || '';
  2058. const fracPart = (fracRaw + '0000').slice(0, 4);
  2059. const base = BigInt(intPart) * 10000n + BigInt(fracPart);
  2060. return sign ? '-' + base.toString() : base.toString();
  2061. };
  2062. const yuanToWanString = (value: unknown): string => {
  2063. const raw = String(value ?? '').trim();
  2064. if (!raw) return '';
  2065. const normalized = raw.replace(/,/g, '');
  2066. const match = normalized.match(/^(-?)(\d+)$/);
  2067. if (!match) return '';
  2068. const sign = match[1] ? '-' : '';
  2069. const amount = BigInt(match[2] || '0');
  2070. const intPart = amount / 10000n;
  2071. const fracPart = amount % 10000n;
  2072. const fracPadded = fracPart.toString().padStart(4, '0');
  2073. const fracTrimmed = fracPadded.replace(/0+$/, '');
  2074. const result = fracTrimmed ? `${intPart.toString()}.${fracTrimmed}` : intPart.toString();
  2075. return sign ? '-' + result : result;
  2076. };
  2077. /** 获取合同列表 */
  2078. const getContractList = async () => {
  2079. const id = route.query.id as string;
  2080. if (!id) return;
  2081. try {
  2082. // 构建查询参数,排除日期字段
  2083. const { contractStartTime, contractEndTime, ...otherParams } = contractSearchParams.value;
  2084. const params: any = {
  2085. pageNum: contractPagination.value.pageNum,
  2086. pageSize: contractPagination.value.pageSize,
  2087. ...otherParams
  2088. };
  2089. // 如果有开始时间,格式化为 YYYY-MM-DD
  2090. if (contractStartTime) {
  2091. const date = new Date(contractStartTime);
  2092. params.contractStartTime = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
  2093. }
  2094. // 如果有结束时间,格式化为 YYYY-MM-DD
  2095. if (contractEndTime) {
  2096. const date = new Date(contractEndTime);
  2097. params.contractEndTime = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
  2098. }
  2099. console.log('合同查询参数:', params); // 添加日志方便调试
  2100. const res = await getSupplierContractsById(id, params);
  2101. contractList.value = (res.rows || []).map((row: any) => ({
  2102. ...row,
  2103. contractAmount: yuanToWanString(row?.contractAmount)
  2104. }));
  2105. contractPagination.value.total = res.total || 0;
  2106. } catch (e) {
  2107. console.error('获取合同列表失败:', e);
  2108. }
  2109. };
  2110. /** 查看附件 */
  2111. const handleViewAttachment = (row: any) => {
  2112. console.log('查看附件:', row);
  2113. if (!row.contractAttachment) {
  2114. ElMessage.warning('该合同没有附件');
  2115. return;
  2116. }
  2117. try {
  2118. // 如果 contractAttachment 是文件ID(数字),使用 oss 方法下载
  2119. if (/^\d+$/.test(row.contractAttachment)) {
  2120. download.oss(row.contractAttachment);
  2121. } else {
  2122. // 如果是文件路径或URL,直接下载
  2123. const link = document.createElement('a');
  2124. link.href = row.contractAttachment;
  2125. link.download = `合同附件_${row.contractName || row.contractNo || 'attachment'}`;
  2126. document.body.appendChild(link);
  2127. link.click();
  2128. document.body.removeChild(link);
  2129. }
  2130. } catch (e) {
  2131. console.error('下载附件失败:', e);
  2132. ElMessage.error('下载附件失败');
  2133. }
  2134. };
  2135. /** 查看合同 */
  2136. const handleViewContract = async (row: any) => {
  2137. try {
  2138. const res = await getContract(row.id);
  2139. Object.assign(contractForm.value, res.data);
  2140. contractForm.value.contractAmount = yuanToWanString((res.data as any)?.contractAmount);
  2141. contractDialogTitle.value = '查看合同';
  2142. contractDialogReadonly.value = true;
  2143. contractDialogVisible.value = true;
  2144. } catch (e) {
  2145. console.error('获取合同详情失败:', e);
  2146. ElMessage.error('获取合同详情失败');
  2147. }
  2148. };
  2149. /** 编辑合同 */
  2150. const handleEditContract = async (row: any) => {
  2151. try {
  2152. const res = await getContract(row.id);
  2153. Object.assign(contractForm.value, res.data);
  2154. contractForm.value.contractAmount = yuanToWanString((res.data as any)?.contractAmount);
  2155. contractDialogTitle.value = '编辑合同';
  2156. contractDialogReadonly.value = false;
  2157. contractDialogVisible.value = true;
  2158. } catch (e) {
  2159. console.error('获取合同详情失败:', e);
  2160. ElMessage.error('获取合同详情失败');
  2161. }
  2162. };
  2163. /** 获取合同类型字典 */
  2164. const getContractTypeDict = async () => {
  2165. try {
  2166. const res = await getDictData('contract_type');
  2167. contractTypeDict.value = res.data || [];
  2168. } catch (e) {
  2169. console.error('获取合同类型字典失败:', e);
  2170. }
  2171. };
  2172. /** 获取税率列表 */
  2173. const getTaxRateData = async () => {
  2174. try {
  2175. const res = await getTaxRateList();
  2176. taxRateList.value = res.rows || res.data || [];
  2177. } catch (e) {
  2178. console.error('获取税率列表失败:', e);
  2179. }
  2180. };
  2181. /** 获取结算方式列表 */
  2182. const getSettlementMethodData = async () => {
  2183. try {
  2184. const res = await getSettlementMethodList();
  2185. settlementMethodList.value = res.rows || res.data || [];
  2186. } catch (e) {
  2187. console.error('获取结算方式列表失败:', e);
  2188. }
  2189. };
  2190. /** 新增合同 */
  2191. const handleAddContract = () => {
  2192. // 重置表单
  2193. contractForm.value = {
  2194. supplierNo: detailData.value.supplierNo,
  2195. supplierId: route.query.id as any,
  2196. contractNo: '',
  2197. contractName: '',
  2198. contractType: '',
  2199. contractStartTime: '',
  2200. contractEndTime: '',
  2201. contractStatus: 0,
  2202. demandReminderTime: 4,
  2203. taxRate: undefined,
  2204. contractAmount: undefined,
  2205. contractDescription: '',
  2206. contractAttachment: '',
  2207. settlementMethod: '',
  2208. invoiceType: ''
  2209. };
  2210. contractDialogTitle.value = '添加合同';
  2211. contractDialogReadonly.value = false;
  2212. contractDialogVisible.value = true;
  2213. };
  2214. /** 提交合同 */
  2215. const handleContractSubmit = async () => {
  2216. if (!contractFormRef.value) return;
  2217. contractFormRef.value.validate(async (valid: boolean) => {
  2218. if (!valid) return;
  2219. try {
  2220. contractSubmitLoading.value = true;
  2221. // 设置供应商编号和ID
  2222. if (!contractForm.value.supplierNo && detailData.value.supplierNo) {
  2223. contractForm.value.supplierNo = detailData.value.supplierNo;
  2224. }
  2225. if (!contractForm.value.supplierId && route.query.id) {
  2226. contractForm.value.supplierId = route.query.id as any;
  2227. }
  2228. const payload = {
  2229. ...contractForm.value,
  2230. contractAmount: wanToYuanString(contractForm.value.contractAmount)
  2231. };
  2232. if ((contractForm.value as any).id) {
  2233. // 更新
  2234. await updateContract(payload);
  2235. ElMessage.success('更新成功');
  2236. } else {
  2237. // 新增
  2238. await addContract(payload);
  2239. ElMessage.success('新增成功');
  2240. }
  2241. contractDialogVisible.value = false;
  2242. // 刷新合同列表
  2243. await getContractList();
  2244. } catch (e) {
  2245. console.error('保存合同失败:', e);
  2246. ElMessage.error('保存合同失败');
  2247. } finally {
  2248. contractSubmitLoading.value = false;
  2249. }
  2250. });
  2251. };
  2252. /** 分页大小改变 */
  2253. const handleContractSizeChange = (size: number) => {
  2254. contractPagination.value.pageSize = size;
  2255. getContractList();
  2256. };
  2257. /** 当前页改变 */
  2258. const handleContractCurrentChange = (page: number) => {
  2259. contractPagination.value.pageNum = page;
  2260. getContractList();
  2261. };
  2262. /** 查看付款信息 */
  2263. const handleViewPayment = async (row: any) => {
  2264. try {
  2265. const res = await getBank(row.id);
  2266. Object.assign(paymentForm.value, res.data);
  2267. paymentDialogTitle.value = '查看付款信息';
  2268. paymentDialogReadonly.value = true;
  2269. paymentDialogVisible.value = true;
  2270. } catch (e) {
  2271. console.error('获取付款信息详情失败:', e);
  2272. ElMessage.error('获取付款信息详情失败');
  2273. }
  2274. };
  2275. /** 编辑付款信息 */
  2276. const handleEditPayment = async (row: any) => {
  2277. try {
  2278. const res = await getBank(row.id);
  2279. Object.assign(paymentForm.value, res.data);
  2280. // 从供应商详情中获取企业工商名称和工商地址
  2281. paymentForm.value.businessName = detailData.value.businessName || '';
  2282. paymentForm.value.businessAddress = detailData.value.businessAddress || '';
  2283. // 纳税人识别号使用社会信用代码
  2284. paymentForm.value.circlesName = detailData.value.socialCreditCode || paymentForm.value.circlesName || '';
  2285. paymentDialogTitle.value = '编辑付款信息';
  2286. paymentDialogReadonly.value = false;
  2287. paymentDialogVisible.value = true;
  2288. } catch (e) {
  2289. console.error('获取付款信息详情失败:', e);
  2290. ElMessage.error('获取付款信息详情失败');
  2291. }
  2292. };
  2293. /** 新增付款信息 */
  2294. const handleAddPayment = () => {
  2295. // 检查是否已保存供应商基本信息
  2296. if (!detailData.value.id || !detailData.value.supplierNo) {
  2297. ElMessage.warning('请先保存供应商基本信息后再新增付款信息');
  2298. return;
  2299. }
  2300. // 重置表单
  2301. paymentForm.value = {
  2302. num: undefined,
  2303. supplierNo: detailData.value.supplierNo,
  2304. supplierId: detailData.value.id,
  2305. bankNum: '',
  2306. bankInfoNo: undefined,
  2307. bankName: '',
  2308. bankNo: '',
  2309. isture: '1',
  2310. circlesName: detailData.value.socialCreditCode || '',
  2311. phone: '',
  2312. invoiceTypeNo: undefined,
  2313. invoiceTypeName: '',
  2314. businessName: detailData.value.businessName || '',
  2315. businessAddress: detailData.value.businessAddress || ''
  2316. };
  2317. paymentDialogTitle.value = '新增付款信息';
  2318. paymentDialogReadonly.value = false;
  2319. paymentDialogVisible.value = true;
  2320. };
  2321. /** 发票类型选择改变 */
  2322. const handleInvoiceTypeChange = (value: string | number) => {
  2323. const selectedInvoice = invoiceTypeList.value.find(item => item.id === value);
  2324. if (selectedInvoice) {
  2325. paymentForm.value.invoiceTypeNo = selectedInvoice.id;
  2326. paymentForm.value.invoiceTypeName = selectedInvoice.invoiceTypeName;
  2327. }
  2328. };
  2329. /** 银行选择改变 */
  2330. const handleBankChange = (value: string | number) => {
  2331. const selectedBank = systemBankList.value.find(item => item.id === value);
  2332. if (selectedBank) {
  2333. paymentForm.value.bankInfoNo = selectedBank.id;
  2334. paymentForm.value.bankName = selectedBank.bnName;
  2335. }
  2336. };
  2337. /** 提交付款信息 */
  2338. const handlePaymentSubmit = async () => {
  2339. if (!paymentFormRef.value) return;
  2340. paymentFormRef.value.validate(async (valid: boolean) => {
  2341. if (!valid) return;
  2342. try {
  2343. paymentSubmitLoading.value = true;
  2344. // 纳税人识别号使用社会信用代码
  2345. paymentForm.value.circlesName = detailData.value.socialCreditCode || paymentForm.value.circlesName || '';
  2346. // 设置供应商编号和ID
  2347. if (!paymentForm.value.supplierNo && detailData.value.supplierNo) {
  2348. paymentForm.value.supplierNo = detailData.value.supplierNo;
  2349. }
  2350. if (!paymentForm.value.supplierId && detailData.value.id) {
  2351. paymentForm.value.supplierId = detailData.value.id;
  2352. }
  2353. if ((paymentForm.value as any).id) {
  2354. // 更新
  2355. await updateBank(paymentForm.value);
  2356. ElMessage.success('更新成功');
  2357. } else {
  2358. // 新增
  2359. await addBank(paymentForm.value);
  2360. ElMessage.success('新增成功');
  2361. }
  2362. paymentDialogVisible.value = false;
  2363. // 刷新付款信息列表
  2364. await getPaymentInfo();
  2365. } catch (e) {
  2366. console.error('保存付款信息失败:', e);
  2367. ElMessage.error('保存付款信息失败');
  2368. } finally {
  2369. paymentSubmitLoading.value = false;
  2370. }
  2371. });
  2372. };
  2373. /** 获取地址列表 */
  2374. const getAddressList = async () => {
  2375. const id = route.query.id as string;
  2376. // add状态不查询地址列表(新增时没有地址)
  2377. if (!id || isAddMode.value) {
  2378. addressList.value = [];
  2379. return;
  2380. }
  2381. try {
  2382. const res = await listAddress({
  2383. supplierId: id, // 使用supplierId作为查询条件
  2384. pageNum: 1,
  2385. pageSize: 1000
  2386. });
  2387. addressList.value = res.data || res.rows || [];
  2388. } catch (e) {
  2389. console.error('获取地址列表失败:', e);
  2390. }
  2391. };
  2392. /** 新增地址 */
  2393. const handleAddAddress = () => {
  2394. // 重置表单
  2395. addressForm.value = {
  2396. supplierNo: detailData.value.supplierNo,
  2397. supplierId: detailData.value.id,
  2398. shipperName: '',
  2399. shipperPhone: '',
  2400. shippingPostCode: '',
  2401. shippingProvincial: '',
  2402. shippingCity: '',
  2403. shippingCounty: '',
  2404. shippingAddress: '',
  2405. isSelf: 0
  2406. };
  2407. selectedAddressRegion.value = [];
  2408. addressDialogTitle.value = '添加地址';
  2409. addressDialogReadonly.value = false;
  2410. addressDialogVisible.value = true;
  2411. };
  2412. /** 查看地址 */
  2413. const handleViewAddress = async (row: any) => {
  2414. try {
  2415. const res = await getAddress(row.id);
  2416. Object.assign(addressForm.value, res.data);
  2417. // 回显省市区选择器
  2418. if (res.data.shippingProvincial && res.data.shippingCity && res.data.shippingCounty) {
  2419. const province = res.data.shippingProvincial;
  2420. const city = res.data.shippingCity;
  2421. const county = res.data.shippingCounty;
  2422. // 根据省市区名称查找对应的代码
  2423. const provinceItem = regionOptions.value.find((p: any) => p.label === province);
  2424. if (provinceItem) {
  2425. const cityItem = provinceItem.children?.find((c: any) => c.label === city);
  2426. if (cityItem) {
  2427. const districtItem = cityItem.children?.find((d: any) => d.label === county);
  2428. if (districtItem) {
  2429. selectedAddressRegion.value = [provinceItem.value, cityItem.value, districtItem.value];
  2430. }
  2431. }
  2432. }
  2433. }
  2434. addressDialogTitle.value = '查看地址';
  2435. addressDialogReadonly.value = true;
  2436. addressDialogVisible.value = true;
  2437. } catch (e) {
  2438. console.error('获取地址详情失败:', e);
  2439. ElMessage.error('获取地址详情失败');
  2440. }
  2441. };
  2442. /** 编辑地址 */
  2443. const handleEditAddress = async (row: any) => {
  2444. try {
  2445. const res = await getAddress(row.id);
  2446. Object.assign(addressForm.value, res.data);
  2447. // 回显省市区选择器
  2448. if (res.data.shippingProvincial && res.data.shippingCity && res.data.shippingCounty) {
  2449. const province = res.data.shippingProvincial;
  2450. const city = res.data.shippingCity;
  2451. const county = res.data.shippingCounty;
  2452. // 根据省市区名称查找对应的代码
  2453. const provinceItem = regionOptions.value.find((p: any) => p.label === province);
  2454. if (provinceItem) {
  2455. const cityItem = provinceItem.children?.find((c: any) => c.label === city);
  2456. if (cityItem) {
  2457. const districtItem = cityItem.children?.find((d: any) => d.label === county);
  2458. if (districtItem) {
  2459. selectedAddressRegion.value = [provinceItem.value, cityItem.value, districtItem.value];
  2460. }
  2461. }
  2462. }
  2463. }
  2464. addressDialogTitle.value = '编辑地址';
  2465. addressDialogReadonly.value = false;
  2466. addressDialogVisible.value = true;
  2467. } catch (e) {
  2468. console.error('获取地址详情失败:', e);
  2469. ElMessage.error('获取地址详情失败');
  2470. }
  2471. };
  2472. /** 删除地址 */
  2473. const handleDeleteAddress = async (row: any) => {
  2474. try {
  2475. await ElMessageBox.confirm('确认删除该地址吗?', '提示', {
  2476. confirmButtonText: '确认',
  2477. cancelButtonText: '取消',
  2478. type: 'warning'
  2479. });
  2480. await delAddress(row.id);
  2481. ElMessage.success('删除成功');
  2482. // 刷新地址列表
  2483. getAddressList();
  2484. } catch (e: any) {
  2485. if (e !== 'cancel') {
  2486. console.error('删除地址失败:', e);
  2487. ElMessage.error('删除地址失败');
  2488. }
  2489. }
  2490. };
  2491. /** 地址省市区改变事件 */
  2492. const handleAddressRegionChange = (value: string[]) => {
  2493. if (value && value.length === 3) {
  2494. // value 是 [省代码, 市代码, 区代码]
  2495. const [provinceCode, cityCode, districtCode] = value;
  2496. // 从 regionOptions 中查找对应的名称
  2497. let provinceName = '';
  2498. let cityName = '';
  2499. let districtName = '';
  2500. const province = regionOptions.value.find((p: any) => p.value === provinceCode);
  2501. if (province) {
  2502. provinceName = province.label;
  2503. const city = province.children?.find((c: any) => c.value === cityCode);
  2504. if (city) {
  2505. cityName = city.label;
  2506. const district = city.children?.find((d: any) => d.value === districtCode);
  2507. if (district) {
  2508. districtName = district.label;
  2509. }
  2510. }
  2511. }
  2512. // 更新 addressForm
  2513. addressForm.value.shippingProvincial = provinceName;
  2514. addressForm.value.shippingCity = cityName;
  2515. addressForm.value.shippingCounty = districtName;
  2516. } else {
  2517. // 清空
  2518. addressForm.value.shippingProvincial = '';
  2519. addressForm.value.shippingCity = '';
  2520. addressForm.value.shippingCounty = '';
  2521. }
  2522. };
  2523. /** 提交地址 */
  2524. const handleAddressSubmit = async () => {
  2525. if (!addressFormRef.value) return;
  2526. addressFormRef.value.validate(async (valid: boolean) => {
  2527. if (!valid) return;
  2528. try {
  2529. addressSubmitLoading.value = true;
  2530. // 设置供应商编号和ID
  2531. if (!addressForm.value.supplierNo && detailData.value.supplierNo) {
  2532. addressForm.value.supplierNo = detailData.value.supplierNo;
  2533. }
  2534. if (!addressForm.value.supplierId && detailData.value.id) {
  2535. addressForm.value.supplierId = detailData.value.id;
  2536. }
  2537. if ((addressForm.value as any).id) {
  2538. // 更新
  2539. await updateAddress(addressForm.value);
  2540. ElMessage.success('更新成功');
  2541. } else {
  2542. // 新增
  2543. await addAddress(addressForm.value);
  2544. ElMessage.success('新增成功');
  2545. }
  2546. addressDialogVisible.value = false;
  2547. // 刷新地址列表
  2548. await getAddressList();
  2549. } catch (e) {
  2550. console.error('保存地址失败:', e);
  2551. ElMessage.error('保存地址失败');
  2552. } finally {
  2553. addressSubmitLoading.value = false;
  2554. }
  2555. });
  2556. };
  2557. onMounted(async () => {
  2558. // 先初始化省市区数据
  2559. initRegionOptions();
  2560. // 初始化供货区域选项数据
  2561. initSupplyAreaOptions();
  2562. // 并行加载所有下拉框选项数据
  2563. await Promise.all([
  2564. getStaffOptions(),
  2565. getCompanyOptions(), // 获取公司下拉选项
  2566. getEnterpriseScaleOptions(), // 获取企业规模下拉选项
  2567. getIndustryCategoryOptions(), // 获取行业类别下拉选项
  2568. getSupplierLevelOptions(), // 获取供应商等级下拉选项
  2569. getSupplierTypeOptions(), // 获取供应商类型下拉选项
  2570. getContractTypeDict(), // 获取合同类型字典
  2571. getTaxRateData(), // 获取税率列表
  2572. getSettlementMethodData(), // 获取结算方式列表
  2573. getInvoiceTypeData(), // 获取发票类型列表
  2574. getAllBrandList(), // 获取所有品牌列表
  2575. getInvoiceTypeData(), // 获取开票类型列表
  2576. getSystemBankData() // 获取银行列表
  2577. ]);
  2578. // 下拉框选项加载完成后,再获取详情数据,确保下拉框能正确显示文字标签
  2579. await getDetail();
  2580. // 只有在编辑模式或基础信息已保存时才加载其他数据
  2581. const id = route.query.id as string;
  2582. const mode = route.query.mode as string;
  2583. if (id && mode !== 'add') {
  2584. getContactList();
  2585. getProductCategories(); // 这个方法内部会调用 getSupplierSelectedCategories
  2586. getContractList(); // 获取合同列表
  2587. getPaymentInfo(); // 获取付款信息
  2588. getAuthorizationList(); // 获取授权详情列表
  2589. getAddressList(); // 获取地址列表
  2590. getSupplyAreaList(); // 获取供货区域列表
  2591. }
  2592. });
  2593. </script>
  2594. <style scoped>
  2595. .app-container {
  2596. background: #f0f2f5;
  2597. min-height: 100vh;
  2598. padding: 0;
  2599. }
  2600. .detail-header {
  2601. background: #fff;
  2602. padding: 16px 24px;
  2603. display: flex;
  2604. align-items: center;
  2605. border-bottom: 1px solid #e8e8e8;
  2606. margin-bottom: 0;
  2607. }
  2608. .back-icon {
  2609. font-size: 18px;
  2610. cursor: pointer;
  2611. margin-right: 12px;
  2612. color: #666;
  2613. }
  2614. .back-icon:hover {
  2615. color: #409eff;
  2616. }
  2617. .header-title {
  2618. font-size: 16px;
  2619. font-weight: 500;
  2620. color: #333;
  2621. }
  2622. .detail-tabs {
  2623. background: #fff;
  2624. margin: 0;
  2625. padding: 0 24px;
  2626. margin-top: 16px;
  2627. }
  2628. .detail-tabs :deep(.el-tabs__header) {
  2629. margin: 0;
  2630. border-bottom: 1px solid #e8e8e8;
  2631. }
  2632. .detail-tabs :deep(.el-tabs__nav-wrap) {
  2633. padding: 0;
  2634. }
  2635. .detail-tabs :deep(.el-tabs__item) {
  2636. height: 48px;
  2637. line-height: 48px;
  2638. font-size: 14px;
  2639. color: #666;
  2640. }
  2641. .detail-tabs :deep(.el-tabs__item.is-active) {
  2642. color: #409eff;
  2643. font-weight: 500;
  2644. }
  2645. .app-container :deep(.tab-content) {
  2646. padding: 24px;
  2647. background: #fff;
  2648. }
  2649. .app-container :deep(.info-section) {
  2650. margin-bottom: 40px;
  2651. }
  2652. .app-container :deep(.section-title) {
  2653. font-size: 15px;
  2654. font-weight: 500;
  2655. color: #333;
  2656. margin-bottom: 24px;
  2657. padding-bottom: 12px;
  2658. border-bottom: 1px solid #e8e8e8;
  2659. }
  2660. .app-container :deep(.section-title-row) {
  2661. display: flex;
  2662. justify-content: space-between;
  2663. align-items: center;
  2664. margin-bottom: 24px;
  2665. padding-bottom: 12px;
  2666. border-bottom: 1px solid #e8e8e8;
  2667. }
  2668. .app-container :deep(.section-title-left) {
  2669. display: flex;
  2670. align-items: center;
  2671. }
  2672. .app-container :deep(.section-title-text) {
  2673. font-size: 15px;
  2674. font-weight: 500;
  2675. color: #409eff;
  2676. }
  2677. .app-container :deep(.section-title-divider) {
  2678. margin: 0 8px;
  2679. color: #999;
  2680. }
  2681. .app-container :deep(.supplier-no) {
  2682. color: #409eff;
  2683. font-weight: normal;
  2684. font-size: 14px;
  2685. }
  2686. .detail-form :deep(.el-form-item) {
  2687. margin-bottom: 18px;
  2688. }
  2689. .detail-form :deep(.el-form-item__label) {
  2690. color: #666;
  2691. font-size: 14px;
  2692. }
  2693. .detail-form :deep(.el-form-item__label::before) {
  2694. color: #f56c6c;
  2695. }
  2696. .app-container :deep(.form-row) {
  2697. margin-bottom: 20px;
  2698. }
  2699. .app-container :deep(.form-item) {
  2700. display: flex;
  2701. align-items: flex-start;
  2702. line-height: 32px;
  2703. font-size: 14px;
  2704. }
  2705. .app-container :deep(.form-item .label) {
  2706. color: #666;
  2707. min-width: 100px;
  2708. flex-shrink: 0;
  2709. }
  2710. .app-container :deep(.form-item .value) {
  2711. color: #333;
  2712. flex: 1;
  2713. word-break: break-all;
  2714. }
  2715. .app-container :deep(.category-group) {
  2716. display: flex;
  2717. flex-wrap: wrap;
  2718. gap: 16px;
  2719. }
  2720. .app-container :deep(.category-group .el-checkbox) {
  2721. margin-right: 0;
  2722. margin-bottom: 12px;
  2723. }
  2724. .app-container :deep(.supply-brand) {
  2725. font-size: 14px;
  2726. color: #333;
  2727. padding: 8px 0;
  2728. }
  2729. .app-container :deep(.brand-input-wrapper) {
  2730. margin-top: 12px;
  2731. }
  2732. .app-container :deep(.brand-dialog .el-dialog__header) {
  2733. padding: 16px 20px 12px;
  2734. border-bottom: 1px solid #ebeef5;
  2735. }
  2736. .app-container :deep(.brand-dialog .el-dialog__title) {
  2737. font-size: 16px;
  2738. font-weight: 600;
  2739. color: #303133;
  2740. }
  2741. .app-container :deep(.brand-dialog .el-dialog__body) {
  2742. padding: 16px 20px 18px;
  2743. }
  2744. .app-container :deep(.brand-dialog__search) {
  2745. padding: 12px 12px 8px;
  2746. background: #f7f9fc;
  2747. border: 1px solid #ebeef5;
  2748. border-radius: 10px;
  2749. margin-bottom: 14px;
  2750. }
  2751. .app-container :deep(.brand-dialog__search .el-input__wrapper) {
  2752. border-radius: 8px;
  2753. }
  2754. .app-container :deep(.brand-search-results) {
  2755. border-radius: 10px;
  2756. border: 1px solid #ebeef5;
  2757. box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
  2758. }
  2759. .app-container :deep(.brand-display-wrapper) {
  2760. margin-top: 12px;
  2761. min-height: 40px;
  2762. padding: 8px;
  2763. background: #f5f7fa;
  2764. border-radius: 4px;
  2765. }
  2766. .app-container :deep(.brand-tags-container) {
  2767. min-height: 80px;
  2768. padding: 16px;
  2769. background: #f5f7fa;
  2770. border-radius: 4px;
  2771. border: 1px solid #e4e7ed;
  2772. }
  2773. .app-container :deep(.brand-search-results) {
  2774. max-height: 300px;
  2775. overflow-y: auto;
  2776. margin-bottom: 20px;
  2777. border: 1px solid #e4e7ed;
  2778. border-radius: 4px;
  2779. }
  2780. .app-container :deep(.search-results-title) {
  2781. padding: 10px 16px;
  2782. background: #f7f9fc;
  2783. font-weight: 500;
  2784. font-size: 14px;
  2785. color: #606266;
  2786. border-bottom: 1px solid #ebeef5;
  2787. }
  2788. .app-container :deep(.brand-result-item) {
  2789. padding: 12px 16px;
  2790. cursor: pointer;
  2791. display: flex;
  2792. justify-content: space-between;
  2793. align-items: center;
  2794. border-bottom: 1px solid #f2f4f7;
  2795. transition: background-color 0.2s;
  2796. }
  2797. .app-container :deep(.brand-result-item:hover) {
  2798. background-color: #f7f9fc;
  2799. }
  2800. .app-container :deep(.brand-result-item:active) {
  2801. background-color: #eef5ff;
  2802. }
  2803. .app-container :deep(.brand-result-item:last-child) {
  2804. border-bottom: none;
  2805. }
  2806. .app-container :deep(.brand-result-name) {
  2807. font-size: 14px;
  2808. color: #303133;
  2809. font-weight: 500;
  2810. }
  2811. .app-container :deep(.brand-result-no) {
  2812. font-size: 12px;
  2813. color: #909399;
  2814. }
  2815. .app-container :deep(.selected-brands-section) {
  2816. margin-top: 20px;
  2817. }
  2818. .app-container :deep(.section-label) {
  2819. font-size: 14px;
  2820. color: #606266;
  2821. margin-bottom: 12px;
  2822. font-weight: 500;
  2823. }
  2824. .app-container :deep(.brand-tags-container) {
  2825. background: #fff;
  2826. border: 1px dashed #dcdfe6;
  2827. border-radius: 10px;
  2828. min-height: 64px;
  2829. padding: 12px;
  2830. }
  2831. .app-container :deep(.brand-tags-container .el-tag) {
  2832. border-radius: 8px;
  2833. }
  2834. .app-container :deep(.image-upload) {
  2835. display: inline-block;
  2836. margin-left: 10px;
  2837. }
  2838. .app-container :deep(.upload-placeholder) {
  2839. width: 100px;
  2840. height: 100px;
  2841. border: 1px dashed #d9d9d9;
  2842. border-radius: 4px;
  2843. display: flex;
  2844. align-items: center;
  2845. justify-content: center;
  2846. cursor: pointer;
  2847. color: #999;
  2848. font-size: 24px;
  2849. }
  2850. .app-container :deep(.upload-placeholder:hover) {
  2851. border-color: #409eff;
  2852. color: #409eff;
  2853. }
  2854. </style>