releaseProduct.vue 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757
  1. <template>
  2. <view class="container">
  3. <view class="form-container">
  4. <up-form labelPosition="left" :model="formData" :rules="rules" ref="formRef" :labelWidth="100">
  5. <!-- 商品分类 -->
  6. <view class="card-title" v-if="!isProductCenter">商品分类</view>
  7. <up-form-item class="form-item" label="商品分类" prop="categoryIds" :borderBottom="false"
  8. @click="showCategory = true" :required="true" v-if="!isProductCenter">
  9. <up-input v-model="formData.categoryDisplayName" disabled disabledColor="#ffffff"
  10. placeholder="请选择商品分类" inputAlign="right" border="none"></up-input>
  11. <template #right>
  12. <up-icon name="arrow-right"></up-icon>
  13. </template>
  14. </up-form-item>
  15. <!-- 商品类目 -->
  16. <view class="card-title" v-if="!isProductCenter">商品类目</view>
  17. <up-form-item class="form-item" label="商品类目" prop="merchantCategoryId" :borderBottom="false"
  18. @click="showMerchantCategory = true" :required="true" v-if="!isProductCenter">
  19. <up-input v-model="formData.merchantCategoryDisplayName" disabled disabledColor="#ffffff"
  20. placeholder="请选择商品类目" inputAlign="right" border="none"></up-input>
  21. <template #right>
  22. <up-icon name="arrow-right"></up-icon>
  23. </template>
  24. </up-form-item>
  25. <!-- 商品信息 -->
  26. <view class="card-title">商品信息</view>
  27. <up-form-item label="商品名称" prop="storeName" :borderBottom="false" :required="true">
  28. <up-input v-model="formData.storeName" placeholder="请输入商品名称" inputAlign="right"
  29. border="none"></up-input>
  30. </up-form-item>
  31. <up-form-item label="商品关键字" prop="keyword" :borderBottom="false" :required="true"
  32. v-if="!isProductCenter">
  33. <up-input v-model="formData.keyword" placeholder="请输入商品关键字" inputAlign="right"
  34. border="none"></up-input>
  35. </up-form-item>
  36. <up-form-item label="商品简介" prop="storeInfo" :borderBottom="false" :required="true"
  37. v-if="!isProductCenter">
  38. <up-textarea v-model="formData.storeInfo" placeholder="请输入商品简介" inputAlign="right"
  39. border="none"></up-textarea>
  40. </up-form-item>
  41. <up-form-item label="单位" prop="unitName" :borderBottom="false" :required="true" v-if="!isProductCenter">
  42. <up-input v-model="formData.unitName" placeholder="请输入单位" inputAlign="right"
  43. border="none"></up-input>
  44. </up-form-item>
  45. <!-- 图片上传 -->
  46. <view class="upload-section" v-if="!isProductCenter">
  47. <view class="upload-item">
  48. <view class="upload-label"><text class="required">*</text>商品封面图</view>
  49. <up-upload @afterRead="async (e) => {
  50. await afterRead(e);
  51. getImage();
  52. }" @delete="onPreviewImageDelete" name="preview" max-count="1" :fileList="previewImages">
  53. <view class="upload-btn">
  54. <up-icon name="plus" size="20" color="#ccc"></up-icon>
  55. <text class="upload-tip">点击上传</text>
  56. </view>
  57. </up-upload>
  58. <text class="format-tip">支持上传PNG、JPG格式的图片,每张不超过5MB。</text>
  59. </view>
  60. </view>
  61. <view class="upload-section" v-if="!isProductCenter">
  62. <view class="upload-item">
  63. <view class="upload-label"><text class="required">*</text>商品图片</view>
  64. <up-upload @afterRead="async (e) => {
  65. await afterRead(e);
  66. getImageProduct();
  67. }" @delete="onProductImageDelete" name="product" multiple :maxCount="10" :fileList="productImages">
  68. <view class="upload-btn">
  69. <up-icon name="plus" size="20" color="#ccc"></up-icon>
  70. <text class="upload-tip">点击上传</text>
  71. </view>
  72. </up-upload>
  73. <text class="format-tip">支持上传PNG、JPG格式图片,每张不超过5MB,最多可上传10张。</text>
  74. </view>
  75. </view>
  76. <!-- 商品属性 -->
  77. <view class="card-title">商品属性</view>
  78. <up-form-item label="材质" prop="metalType" :borderBottom="false" :required="true"
  79. v-if="!isProductCenter">
  80. <up-radio-group v-model="formData.metalType" placement="row">
  81. <up-radio :customStyle="{marginRight: '20rpx'}" v-for="(item, index) in materialList"
  82. :key="index" :label="item.name" :name="item.code" activeColor="#F8C008">
  83. </up-radio>
  84. </up-radio-group>
  85. </up-form-item>
  86. <up-form-item class="form-item" label="运费模板" prop="tempIds" :borderBottom="false"
  87. @click="showTemp = true" :required="true">
  88. <up-input v-model="formData.tempName" disabled disabledColor="#ffffff" placeholder="请选择运费模板"
  89. inputAlign="right" border="none"></up-input>
  90. <template #right>
  91. <up-icon name="arrow-right"></up-icon>
  92. </template>
  93. </up-form-item>
  94. <!-- 商品描述 -->
  95. <view class="editor-section" v-if="!isProductCenter">
  96. <view class="editor-header">
  97. <text class="editor-label">商品描述</text>
  98. <text class="word-count">{{ descriptionText.length }}/500</text>
  99. </view>
  100. <sp-editor editorId="editor" :toolbar-config="toolbarConfig" :readOnly="readOnly"
  101. @input="onEditorInput" @upinImage="onUpinImage" @init="onEditorInit"
  102. @overMax="onOverMax"></sp-editor>
  103. </view>
  104. <!-- 商品规格批量设置 -->
  105. <view class="pro-cate-container">
  106. <view class="cate-title" v-if="!isProductCenter">商品规格批量设置</view>
  107. <view class="cate-list">
  108. <view class="cate-item">
  109. <view class="cate-pic-line">
  110. <up-upload :fileList="oneTemplate.imgs" @afterRead="async (e) => {
  111. await afterRead(e);
  112. getImageProductGg('oneTemplate');
  113. }" @delete="onProductImageGgDelete('oneTemplate')" name="productGg" :maxCount="1">
  114. <view class="upload-btn">
  115. <up-icon name="plus" size="20" color="#666"></up-icon>
  116. <text class="upload-tip">上传图片</text>
  117. </view>
  118. </up-upload>
  119. </view>
  120. <view class="cate-ipt-line">
  121. <view class="cate-ipt" style="margin-right: 16rpx;">
  122. <up-input class="" v-model="oneTemplate.price" placeholder="请输入工费" inputAlign="left"
  123. @blur="keyupEvent('price', oneTemplate.price, 0, 1)" border="none" type="digit"
  124. labelWidth="">
  125. <template #suffix>
  126. <text class="unit">元/g</text>
  127. </template>
  128. </up-input>
  129. </view>
  130. <view class="cate-ipt">
  131. <up-input class="" v-model="oneTemplate.stock" placeholder="请输入库存" inputAlign="left"
  132. @blur="keyupEvent('stock', oneTemplate.stock, 0, 1)" border="none" type="number"
  133. labelWidth="">
  134. <template #suffix>
  135. <text class="unit">件</text>
  136. </template>
  137. </up-input>
  138. </view>
  139. </view>
  140. <view class="cate-ipt-line">
  141. <view class="cate-ipt" style="margin-right: 16rpx;">
  142. <up-input class="" v-model="oneTemplate.weight" placeholder="请输入重量"
  143. inputAlign="left" @blur="keyupEvent('weight', oneTemplate.weight, 0, 1)"
  144. border="none" type="digit" labelWidth="">
  145. <template #suffix>
  146. <text class="unit">g</text>
  147. </template>
  148. </up-input>
  149. </view>
  150. <view class="cate-ipt">
  151. <up-input class="" v-model="oneTemplate.additionalAmount" placeholder="请输入附加金额"
  152. inputAlign="left"
  153. @blur="keyupEvent('additionalAmount', oneTemplate.additionalAmount, 0, 1)"
  154. border="none" type="number" labelWidth="">
  155. <template #suffix>
  156. <text class="unit">元</text>
  157. </template>
  158. </up-input>
  159. </view>
  160. </view>
  161. <view class="cate-ipt-line" style="padding: 0;">
  162. <view class="cate-ipt">
  163. <up-input class="" v-model="oneTemplate.barCode" placeholder="请输入商品编号"
  164. inputAlign="left" @blur="keyupEvent('barCode', oneTemplate.barCode, 0, 1)"
  165. border="none" type="digit" labelWidth="">
  166. </up-input>
  167. </view>
  168. </view>
  169. </view>
  170. </view>
  171. <view class="cate-add" @click="setAllAttr">
  172. 批量设置规格
  173. </view>
  174. </view>
  175. <!-- 商品规格管理 -->
  176. <view class="pro-cate-container">
  177. <view class="cate-title" v-if="!isProductCenter"><text style="color:red;">*</text>商品规格</view>
  178. <view class="cate-list">
  179. <view class="cate-item" v-for="(item,index) of attrTable" :key="'attr'+index">
  180. <view class="cate-pic-line">
  181. <up-upload :fileList="item.imgs" @afterRead="async (e) => {
  182. await afterRead(e);
  183. getImageProductGg(index);
  184. }" @delete="onProductImageGgDelete(index)" name="productGg" :maxCount="1">
  185. <view class="upload-btn">
  186. <up-icon name="plus" size="20" color="#666"></up-icon>
  187. <text class="upload-tip">上传图片</text>
  188. </view>
  189. </up-upload>
  190. <view class="cate-name">
  191. {{formatterAttrValue(item)}}
  192. </view>
  193. </view>
  194. <view class="cate-ipt-line">
  195. <view class="cate-ipt" style="margin-right: 16rpx;">
  196. <up-input class="" v-model="item.price" placeholder="请输入工费" inputAlign="left"
  197. @blur="keyupEvent('price', item.price, index, 2)" border="none" type="digit"
  198. labelWidth="">
  199. <template #suffix>
  200. <text class="unit">元/g</text>
  201. </template>
  202. </up-input>
  203. </view>
  204. <view class="cate-ipt">
  205. <up-input class="" v-model="item.stock" placeholder="请输入库存" inputAlign="left"
  206. @blur="keyupEvent('stock', item.stock, index, 2)" border="none" type="number"
  207. labelWidth="">
  208. <template #suffix>
  209. <text class="unit">件</text>
  210. </template>
  211. </up-input>
  212. </view>
  213. </view>
  214. <view class="cate-ipt-line">
  215. <view class="cate-ipt" style="margin-right: 16rpx;">
  216. <up-input class="" v-model="item.weight" placeholder="请输入重量" inputAlign="left"
  217. @blur="keyupEvent('weight', item.weight, index, 2)" border="none" type="digit"
  218. labelWidth="">
  219. <template #suffix>
  220. <text class="unit">g</text>
  221. </template>
  222. </up-input>
  223. </view>
  224. <view class="cate-ipt">
  225. <up-input class="" v-model="item.additionalAmount" placeholder="请输入附加金额"
  226. inputAlign="left"
  227. @blur="keyupEvent('additionalAmount', item.additionalAmount, index, 2)"
  228. border="none" type="number" labelWidth="">
  229. <template #suffix>
  230. <text class="unit">元</text>
  231. </template>
  232. </up-input>
  233. </view>
  234. </view>
  235. <view class="cate-ipt-line" style="padding: 0;">
  236. <view class="cate-ipt">
  237. <up-input class="" v-model="item.barCode" placeholder="请输入商品编号" inputAlign="left"
  238. @blur="keyupEvent('barCode', item.barCode, index, 2)" border="none" type="digit"
  239. labelWidth="">
  240. </up-input>
  241. </view>
  242. </view>
  243. </view>
  244. </view>
  245. <view class="cate-add" @click="editCatePage">
  246. 管理规格
  247. </view>
  248. </view>
  249. <!-- 商品排序 -->
  250. <up-form-item label="商品排序" prop="sort" :borderBottom="false">
  251. <up-input v-model="formData.sort" placeholder="请输入排序号" inputAlign="right" border="none"
  252. type="number"></up-input>
  253. </up-form-item>
  254. </up-form>
  255. </view>
  256. <!-- 发布按钮 -->
  257. <view class="btn-view" :style="btnViewStyle">
  258. <button class="submit" @click="submitForm">立即发布</button>
  259. </view>
  260. <!-- 商品分类选择弹窗 -->
  261. <up-popup :show="showCategory" @close="showCategory = false" mode="bottom" round="20" :closeable="true">
  262. <view class="popup-content">
  263. <view class="popup-header">
  264. <text class="popup-title">选择商品分类</text>
  265. </view>
  266. <category-selector :categoryList="categoryData" :selectedIds="formData.categoryIds"
  267. @change="onCategoryChange" ref="categoryRef" />
  268. <view class="popup-actions">
  269. <button class="action-btn cancel" @click="showCategory = false">取消</button>
  270. <button class="action-btn confirm" @click="confirmCategory">确定</button>
  271. </view>
  272. </view>
  273. </up-popup>
  274. <!-- 商品类目选择弹窗 -->
  275. <up-popup
  276. :show="showMerchantCategory"
  277. @close="showMerchantCategory = false"
  278. mode="bottom"
  279. round="20"
  280. :closeable="true"
  281. >
  282. <view class="popup-content">
  283. <view class="popup-header">
  284. <text class="popup-title">选择商品类目</text>
  285. </view>
  286. <category-selector
  287. :categoryList="merchantCategoryData"
  288. :selectedIds="formData.merchantCategoryId"
  289. @change="onMerchantCategoryChange"
  290. ref="merchantCategoryRef"
  291. :multiple="false"
  292. />
  293. <view class="popup-actions">
  294. <button class="action-btn cancel" @click="showMerchantCategory = false">取消</button>
  295. <button class="action-btn confirm" @click="confirmMerchantCategory">确定</button>
  296. </view>
  297. </view>
  298. </up-popup>
  299. <!-- 运费模板选择器 -->
  300. <up-picker :show="showTemp" v-model="formData.tempIds" :columns="tempColumns" keyName="name" valueName="id"
  301. confirmColor="#F8C008" @close="showTemp = false" @confirm="tempConfirm"
  302. @cancel="showTemp = false"></up-picker>
  303. </view>
  304. </template>
  305. <script setup>
  306. import {
  307. ref,
  308. computed,
  309. watch,
  310. nextTick
  311. } from 'vue';
  312. import {
  313. onShow,
  314. onLoad,
  315. onUnload
  316. } from "@dcloudio/uni-app";
  317. import {
  318. productCategory,
  319. productSave,
  320. productUpdate,
  321. templatesList,
  322. productInfo,
  323. getMerchantCategory
  324. } from "@/api/merchant";
  325. import CategorySelector from '@/components/CategorySelector';
  326. import {
  327. useAppStore
  328. } from "@/stores/app";
  329. import {
  330. useImageUpload
  331. } from "@/hooks/useImageUpload";
  332. import {
  333. useToast
  334. } from "@/hooks/useToast";
  335. const {
  336. Toast
  337. } = useToast();
  338. const {
  339. imageList,
  340. afterRead,
  341. deletePic,
  342. uploadLoading
  343. } = useImageUpload({
  344. pid: 1,
  345. model: "product",
  346. });
  347. const appStore = useAppStore();
  348. const merchantInfo = appStore.userInfo.merchant
  349. // 表单验证和提交
  350. const formRef = ref(null);
  351. // 表单数据
  352. const formData = ref({
  353. categoryIds: [], // 选中的分类ID数组
  354. categoryDisplayName: '', // 显示的分类名称
  355. merchantCategoryId: [], // 选中的商品类目ID数组(单选:长度始终为1)
  356. merchantCategoryDisplayName: '', // 显示的商品类目名称
  357. tempName: '', // 显示的运费模板名称
  358. tempIds: [],
  359. storeName: '',
  360. keyword: '',
  361. storeInfo: '',
  362. unitName: '',
  363. metalType: '',
  364. weight: '',
  365. laborCost: '',
  366. additionalFee: '',
  367. sort: '',
  368. content: '',
  369. stock: '',
  370. barCode: ''
  371. });
  372. // 编辑器相关
  373. const editorIns = ref(null);
  374. const readOnly = ref(false);
  375. const descriptionText = ref('');
  376. const toolbarConfig = ref({
  377. iconSize: '20px',
  378. iconColumns: 10,
  379. excludeKeys: ['direction', 'date', 'lineHeight', 'letterSpacing', 'listCheck']
  380. });
  381. // 分类相关
  382. const showCategory = ref(false);
  383. const categoryData = ref([]);
  384. const categoryRef = ref();
  385. // 商品类目相关
  386. const showMerchantCategory = ref(false);
  387. const merchantCategoryData = ref([]);
  388. const merchantCategoryRef = ref();
  389. const showTemp = ref(false);
  390. const tempColumns = ref([])
  391. // 图片列表
  392. const previewImages = ref([]);
  393. const productImages = ref([]);
  394. const productImagesGg = ref([]);
  395. const productId = ref(null);
  396. const isProductCenter = ref(null);
  397. // 规格相关
  398. const attr = ref([]);
  399. const attrTable = ref([]);
  400. const oneTemplate = ref({
  401. image: '',
  402. imgs: [],
  403. price: '',
  404. additionalAmount: '',
  405. stock: '',
  406. barCode: '',
  407. weight: '',
  408. })
  409. let historyAttr = []
  410. let historyTable = {}
  411. // 材质列表
  412. const materialList = ref([{
  413. name: '黄金',
  414. code: 'au'
  415. },
  416. {
  417. name: '铂金',
  418. code: 'pt'
  419. },
  420. {
  421. name: '白银',
  422. code: 'ag'
  423. }
  424. ]);
  425. // 验证规则
  426. const rules = ref({
  427. categoryIds: {
  428. type: 'array',
  429. required: true,
  430. message: '请选择商品分类',
  431. trigger: ['blur', 'change']
  432. },
  433. merchantCategoryId: {
  434. type: 'array',
  435. required: true,
  436. len: 1,
  437. message: '请选择一个商品类目',
  438. trigger: ['blur','change']
  439. },
  440. storeName: {
  441. type: 'string',
  442. required: true,
  443. message: '请输入商品名称',
  444. trigger: ['blur', 'change']
  445. },
  446. keyword: {
  447. type: 'string',
  448. required: true,
  449. message: '请输入商品关键字',
  450. trigger: ['blur', 'change']
  451. },
  452. storeInfo: {
  453. type: 'string',
  454. required: true,
  455. message: '请输入商品简介',
  456. trigger: ['blur', 'change']
  457. },
  458. unitName: {
  459. type: 'string',
  460. required: true,
  461. message: '请输入单位',
  462. trigger: ['blur', 'change']
  463. },
  464. tempIds: {
  465. type: 'array',
  466. required: true,
  467. message: '请选择运费模板',
  468. trigger: ['blur', 'change']
  469. },
  470. metalType: {
  471. type: 'string',
  472. required: true,
  473. message: '请选择材质',
  474. trigger: ['blur', 'change']
  475. },
  476. sort: {
  477. type: 'string',
  478. pattern: /^\d*$/,
  479. message: '排序号必须是整数',
  480. trigger: ['blur', 'change'],
  481. validator: (rule, value) => {
  482. if (!value || value.trim().length === 0) return true;
  483. return /^\d+$/.test(value);
  484. }
  485. }
  486. });
  487. // 键盘高度监听
  488. const keyboardHeight = ref(0);
  489. // 计算按钮容器的样式
  490. const btnViewStyle = computed(() => {
  491. return {
  492. position: keyboardHeight.value > 0 ? `relative` : 'fixted',
  493. };
  494. });
  495. // 页面生命周期
  496. onShow(() => {
  497. uni.onKeyboardHeightChange((res) => {
  498. console.log('键盘高度变化:', res.height);
  499. keyboardHeight.value = res.height;
  500. });
  501. })
  502. onLoad(async (options) => {
  503. appStore.SET_CPATTR([])
  504. appStore.SET_CPATTRVALUE([])
  505. await getProductCategory();
  506. await getMerchantCategoryList();
  507. await getTempData();
  508. console.log(options)
  509. if (options.id) {
  510. productId.value = options.id;
  511. isProductCenter.value = options.isProductCenter || null;
  512. await getProductDetail(options.id);
  513. } else {
  514. resetForm();
  515. }
  516. uni.$on("updateAttr", onUpdateAttr)
  517. })
  518. onUnload(() => {
  519. uni.$off("updateAttr")
  520. })
  521. // 规格更新回调
  522. const onUpdateAttr = (data) => {
  523. console.log(data)
  524. const formattedData = data.map(item => {
  525. return {
  526. ...item,
  527. attrValue: typeof item.attrValue === 'string' ?
  528. JSON.parse(item.attrValue) :
  529. item.attrValue
  530. };
  531. });
  532. const table = attrFormat(data)
  533. console.log(table)
  534. attr.value = data.concat();
  535. attrTable.value = table
  536. }
  537. // 获取商品分类
  538. async function getProductCategory() {
  539. let obj = {
  540. type: 1,
  541. status: 1
  542. }
  543. try {
  544. const {
  545. data
  546. } = await productCategory(obj)
  547. console.log('原始分类数据:', data);
  548. const newArr = []
  549. data.forEach((value, index) => {
  550. newArr[index] = value
  551. if (value.child) newArr[index].child = value.child.filter(item => item.status === true)
  552. })
  553. categoryData.value = newArr.filter(item => item.code !== 'bb_mall')
  554. console.log('转换后的分类数据:', categoryData.value);
  555. } catch (error) {
  556. console.error('获取商品分类失败:', error);
  557. uni.showToast({
  558. title: '获取分类失败',
  559. icon: 'none'
  560. });
  561. }
  562. }
  563. // 获取商品类目列表
  564. async function getMerchantCategoryList(){
  565. try {
  566. const { data } = await getMerchantCategory();
  567. console.log('原始商品类目数据:', data);
  568. merchantCategoryData.value = data.map(item => ({
  569. id: item.id,
  570. name: item.categoryName,
  571. child: []
  572. }));
  573. console.log('适配后的商品类目数据:', merchantCategoryData.value);
  574. } catch (error) {
  575. console.error('获取商品类目失败:', error);
  576. uni.showToast({ title: '获取商品类目失败', icon: 'none' });
  577. }
  578. }
  579. // 获取运费模板
  580. async function getTempData() {
  581. let obj = {
  582. page: 1,
  583. limit: 9999
  584. }
  585. try {
  586. const {
  587. data
  588. } = await templatesList()
  589. tempColumns.value[0] = data.list;
  590. } catch (error) {
  591. console.error('获取运费模板失败:', error);
  592. uni.showToast({
  593. title: '获取运费模板失败',
  594. icon: 'none'
  595. });
  596. }
  597. }
  598. // 初始化表单验证
  599. const initFormValidation = async () => {
  600. await nextTick();
  601. if (!formRef.value) {
  602. console.error('表单引用不存在');
  603. return;
  604. }
  605. try {
  606. formRef.value.clearValidate();
  607. setTimeout(async () => {
  608. const requiredFields = ['categoryIds', 'merchantCategoryId', 'storeName', 'keyword', 'storeInfo', 'unitName',
  609. 'tempIds', 'metalType'
  610. ];
  611. for (const field of requiredFields) {
  612. try {
  613. await formRef.value.validateField(field);
  614. } catch (error) {
  615. console.log(`⚠️ 字段 ${field} 验证状态:`, error);
  616. }
  617. }
  618. }, 300);
  619. } catch (error) {
  620. console.error('初始化表单验证状态失败:', error);
  621. }
  622. };
  623. // 获取商品详情
  624. async function getProductDetail(id) {
  625. try {
  626. const {
  627. data
  628. } = await productInfo(id)
  629. console.log('获取商品详情:', data);
  630. Object.assign(formData.value, {
  631. ...data,
  632. categoryIds: data.cateId ? (Array.isArray(data.cateId) ? data.cateId : data.cateId.split(',')) : [],
  633. merchantCategoryId: data.merchantCategoryId ? (Array.isArray(data.merchantCategoryId) ? data.merchantCategoryId : [data.merchantCategoryId]) : [],
  634. tempIds: data.tempId ? [data.tempId] : []
  635. });
  636. productImages.value = [];
  637. console.log('分类IDs:', formData.value.categoryIds);
  638. console.log('商品类目IDs:', formData.value.merchantCategoryId);
  639. console.log('运费模板IDs:', formData.value.tempIds);
  640. await nextTick();
  641. formData.value.categoryDisplayName = getCategoryDisplayName(formData.value.categoryIds);
  642. formData.value.merchantCategoryDisplayName = getMerchantCategoryDisplayName(formData.value.merchantCategoryId);
  643. formData.value.tempName = formatterTemp(data.tempId);
  644. // 图片处理
  645. previewImages.value = data.image ? [{
  646. url: data.image
  647. }] : [];
  648. // 商品轮播图
  649. if (data.sliderImage) {
  650. try {
  651. const urlArr = typeof data.sliderImage === 'string' ? JSON.parse(data.sliderImage) : data.sliderImage;
  652. if (urlArr && urlArr.length > 0) {
  653. productImages.value = urlArr.map(url => ({
  654. url
  655. }));
  656. }
  657. } catch (error) {
  658. console.error('解析轮播图失败:', error);
  659. productImages.value = [];
  660. }
  661. }
  662. // 商品属性
  663. if (data.attrValue && data.attrValue.length > 0) {
  664. data.attr.forEach(item => {
  665. attr.value.push({
  666. attrName: item.attrName,
  667. attrValue: item.attrValues.split(',')
  668. })
  669. })
  670. data.attrValue.forEach(item => {
  671. let attrName = item.suk;
  672. attrTable.value.push({
  673. ...item,
  674. attrName: attrName,
  675. imgs: item?.image ? [{
  676. url: item.image
  677. }] : []
  678. })
  679. })
  680. appStore.SET_CPATTR(attr.value)
  681. }
  682. // 商品描述
  683. descriptionText.value = data.content ? data.content.replace(/<[^>]*>/g, '').substring(0, 500) : '';
  684. formData.value.content = data.content || '';
  685. // 设置分类选择器
  686. if (categoryRef.value && formData.value.categoryIds.length > 0) {
  687. await nextTick();
  688. if (categoryRef.value.setSelectedIds) {
  689. categoryRef.value.setSelectedIds(formData.value.categoryIds);
  690. }
  691. }
  692. // 设置商品类目选择器
  693. if (merchantCategoryRef.value && formData.value.merchantCategoryId.length > 0) {
  694. await nextTick();
  695. if (merchantCategoryRef.value.setSelectedIds) {
  696. merchantCategoryRef.value.setSelectedIds(formData.value.merchantCategoryId);
  697. }
  698. }
  699. await initFormValidation();
  700. } catch (error) {
  701. console.error('获取商品详情失败:', error);
  702. uni.showToast({
  703. title: '获取商品详情失败',
  704. icon: 'none'
  705. });
  706. }
  707. }
  708. // 运费模板id转名称
  709. function formatterTemp(id) {
  710. const foundItem = tempColumns.value[0]?.find(item => item.id == id);
  711. return foundItem ? foundItem.name : '';
  712. }
  713. // 运费模板确认选择
  714. function tempConfirm(obj) {
  715. formData.value.tempId = obj.value[0].id;
  716. formData.value.tempName = obj.value[0].name;
  717. showTemp.value = false;
  718. setTimeout(() => {
  719. if (formRef.value) {
  720. formRef.value.validateField('tempIds');
  721. }
  722. }, 100);
  723. }
  724. // 分类选择变化
  725. const onCategoryChange = (result) => {
  726. console.log('分类选择变化:', result);
  727. }
  728. // 商品类目选择变化
  729. const onMerchantCategoryChange = (result) => {
  730. console.log('商品类目选择变化:', result);
  731. }
  732. // 确认分类选择
  733. const confirmCategory = () => {
  734. if (categoryRef.value) {
  735. const selectedIds = categoryRef.value.getSelectedIds()
  736. if (selectedIds.length === 0) {
  737. uni.showToast({
  738. title: '请至少选择一个分类',
  739. icon: 'none'
  740. })
  741. return
  742. }
  743. formData.value.categoryIds = selectedIds
  744. formData.value.categoryDisplayName = getCategoryDisplayName(selectedIds)
  745. showCategory.value = false
  746. console.log('最终选中的分类ID:', selectedIds)
  747. setTimeout(() => {
  748. if (formRef.value) {
  749. formRef.value.validateField('categoryIds');
  750. }
  751. }, 100);
  752. }
  753. }
  754. // 确认商品类目选择
  755. const confirmMerchantCategory = () => {
  756. if (merchantCategoryRef.value) {
  757. let selectedIds = merchantCategoryRef.value.getSelectedIds()
  758. if (selectedIds.length === 0) {
  759. uni.showToast({ title: '请选择一个商品类目', icon: 'none' })
  760. return
  761. }
  762. if (selectedIds.length > 1) {
  763. selectedIds = [selectedIds[0]]
  764. uni.showToast({ title: '商品类目仅支持选择一个', icon: 'none' })
  765. }
  766. formData.value.merchantCategoryId = selectedIds
  767. formData.value.merchantCategoryDisplayName = getMerchantCategoryDisplayName(selectedIds)
  768. showMerchantCategory.value = false
  769. console.log('最终选中的商品类目ID(单选):', selectedIds)
  770. setTimeout(() => {
  771. if (formRef.value) {
  772. formRef.value.validateField('merchantCategoryId');
  773. }
  774. }, 100);
  775. }
  776. }
  777. // 生成分类显示名称
  778. const getCategoryDisplayName = (selectedIds) => {
  779. if (!selectedIds || selectedIds.length === 0) return ''
  780. const names = []
  781. selectedIds.forEach(id => {
  782. const firstLevel = categoryData.value.find(item => item.id == id)
  783. if (firstLevel) {
  784. names.push(`${firstLevel.name}`)
  785. } else {
  786. for (const parent of categoryData.value) {
  787. if (parent.child) {
  788. const secondLevel = parent.child.find(child => child.id == id)
  789. if (secondLevel) {
  790. names.push(`${parent.name}-${secondLevel.name}`)
  791. break
  792. }
  793. }
  794. }
  795. const singleLevel = categoryData.value.find(item =>
  796. !item.child && item.id == id
  797. )
  798. if (singleLevel) {
  799. names.push(singleLevel.name)
  800. }
  801. }
  802. })
  803. return names.join('、')
  804. }
  805. // 生成商品类目显示名称
  806. const getMerchantCategoryDisplayName = (selectedIds) => {
  807. if (!selectedIds || selectedIds.length === 0) return ''
  808. const firstId = selectedIds[0]
  809. const category = merchantCategoryData.value.find(item => item.id == firstId)
  810. return category ? category.name : ''
  811. }
  812. // 图片上传相关
  813. async function getImage() {
  814. console.log(imageList.value)
  815. if (imageList.value.length > 0) {
  816. if (imageList.value[0].status == "success") {
  817. previewImages.value = imageList.value;
  818. } else {
  819. Toast({
  820. title: "上传失败"
  821. });
  822. }
  823. }
  824. imageList.value = [];
  825. }
  826. async function getImageProduct() {
  827. if (imageList.value.length > 0) {
  828. if (imageList.value[0].status == "success") {
  829. productImages.value = [...productImages.value, ...imageList.value];
  830. } else {
  831. Toast({
  832. title: "上传失败"
  833. });
  834. }
  835. }
  836. imageList.value = [];
  837. }
  838. async function getImageProductGg(index) {
  839. if (imageList.value.length > 0) {
  840. if (imageList.value[0].status == "success") {
  841. if (index == "oneTemplate") {
  842. oneTemplate.value.imgs = imageList.value
  843. oneTemplate.value.image = imageList.value[0].info.url
  844. } else {
  845. attrTable.value[index].imgs = imageList.value
  846. attrTable.value[index].image = imageList.value[0].info.url
  847. }
  848. } else {
  849. Toast({
  850. title: "上传失败"
  851. });
  852. }
  853. }
  854. imageList.value = [];
  855. }
  856. const onPreviewImageDelete = (e) => {
  857. previewImages.value.splice(e.index, 1);
  858. };
  859. const onProductImageDelete = (e) => {
  860. productImages.value.splice(e.index, 1);
  861. };
  862. const onProductImageGgDelete = (e) => {
  863. if (e == "oneTemplate") {
  864. oneTemplate.value.imgs = []
  865. } else {
  866. attrTable.value[e].imgs = []
  867. }
  868. productImagesGg.value.splice(e.index, 1);
  869. };
  870. // 编辑器相关方法
  871. const onEditorInput = (e) => {
  872. descriptionText.value = e.text || '';
  873. formData.value.content = e.html || '';
  874. };
  875. const onEditorInit = (editor) => {
  876. editorIns.value = editor;
  877. };
  878. const onOverMax = (e) => {
  879. uni.showToast({
  880. title: '内容长度超出限制',
  881. icon: 'none'
  882. });
  883. };
  884. const onUpinImage = (tempFiles, editorCtx) => {
  885. const filePath = tempFiles[0].tempFilePath || tempFiles[0].path;
  886. editorCtx.insertImage({
  887. src: filePath,
  888. width: '80%',
  889. success: () => {
  890. uni.showToast({
  891. title: '图片插入成功',
  892. icon: 'success'
  893. });
  894. }
  895. });
  896. };
  897. // 表单验证
  898. const validateForm = () => {
  899. try {
  900. if (!isProductCenter.value) {
  901. if (previewImages.value.length === 0) {
  902. uni.showToast({
  903. title: '请上传商品预览图',
  904. icon: 'none'
  905. });
  906. return;
  907. }
  908. if (productImages.value.length === 0) {
  909. uni.showToast({
  910. title: '请上传商品图片',
  911. icon: 'none'
  912. });
  913. return;
  914. }
  915. if (formData.value.sort && !/^\d+$/.test(formData.value.sort)) {
  916. uni.showToast({
  917. title: '排序号必须是整数',
  918. icon: 'none'
  919. });
  920. return;
  921. }
  922. }
  923. const valid = formRef.value.validate();
  924. return valid;
  925. } catch (error) {
  926. console.error('表单验证失败:', error);
  927. return false;
  928. }
  929. };
  930. // 规格验证
  931. const validateAttr = (item) => {
  932. if (!item.image) {
  933. uni.showToast({
  934. title: '请上传商品规格图片',
  935. icon: 'none'
  936. });
  937. return false;
  938. }
  939. if (!/^\d+(\.\d+)?$/.test(item.weight)) {
  940. uni.showToast({
  941. title: '重量格式不正确',
  942. icon: 'none'
  943. });
  944. return false;
  945. }
  946. if (!/^\d+(\.\d+)?$/.test(item.price)) {
  947. uni.showToast({
  948. title: '工费格式不正确',
  949. icon: 'none'
  950. });
  951. return false;
  952. }
  953. if (!/^\d+(\.\d+)?$/.test(item.additionalAmount)) {
  954. uni.showToast({
  955. title: '附加费格式不正确',
  956. icon: 'none'
  957. });
  958. return false;
  959. }
  960. if (!/^\d+$/.test(item.stock)) {
  961. uni.showToast({
  962. title: '库存必须是整数',
  963. icon: 'none'
  964. });
  965. return false;
  966. }
  967. if (!/^\d+$/.test(item.barCode)) {
  968. uni.showToast({
  969. title: '商品编号必须是整数',
  970. icon: 'none'
  971. });
  972. return false;
  973. }
  974. return true
  975. }
  976. // 提交表单
  977. const submitForm = async () => {
  978. const valid = validateForm();
  979. if (valid) {
  980. const submitData = {
  981. ...formData.value
  982. };
  983. let urlString = '';
  984. if (isProductCenter.value) {
  985. submitData.image = previewImages.value[0].url;
  986. urlString = JSON.stringify(productImages.value.map(item => item.url));
  987. } else {
  988. submitData.image = previewImages.value[0]?.info?.url || previewImages.value[0]?.url;
  989. urlString = JSON.stringify(productImages.value.map(item => item?.info?.url || item?.url));
  990. }
  991. submitData.sliderImage = urlString;
  992. submitData.cateId = formData.value.categoryIds.join(',');
  993. submitData.merchantCategoryId = formData.value.merchantCategoryId;
  994. submitData.merchantId = parseInt(merchantInfo.id);
  995. submitData.specType = true;
  996. submitData.isSub = false;
  997. if (!attr.value || attr.value.length <= 0 || !attrTable.value || attrTable.value.length <= 0) {
  998. return Toast({
  999. title: "请添加商品规格!"
  1000. });
  1001. }
  1002. let pass = true;
  1003. submitData.attr = attr.value.map(item => {
  1004. return {
  1005. attrName: item.attrName,
  1006. attrValues: item.attrValue.join(','),
  1007. }
  1008. })
  1009. submitData.attrValue = attrTable.value.map(item => {
  1010. if (!validateAttr(item)) {
  1011. pass = false;
  1012. }
  1013. return {
  1014. ...item,
  1015. id: 0,
  1016. productId: 0,
  1017. attrValue: typeof(item.attrValue) == 'object' ? JSON.stringify(item.attrValue) : item.attrValue,
  1018. image: item.image || item.imgs[0]?.info?.url || item.imgs[0]?.url
  1019. }
  1020. })
  1021. if (!pass) return false;
  1022. if (productId.value && !isProductCenter.value) {
  1023. const {
  1024. data
  1025. } = await productUpdate(submitData);
  1026. uni.showToast({
  1027. title: '修改成功',
  1028. icon: 'success'
  1029. });
  1030. } else {
  1031. const {
  1032. data
  1033. } = await productSave(submitData);
  1034. uni.showToast({
  1035. title: '发布成功',
  1036. icon: 'success'
  1037. });
  1038. }
  1039. if (!!productId.value && !isProductCenter.value) {
  1040. uni.navigateBack()
  1041. } else {
  1042. uni.redirectTo({
  1043. url: '/pages/merchantCenters/productManagement'
  1044. })
  1045. }
  1046. }
  1047. }
  1048. // 重置表单
  1049. const resetForm = () => {
  1050. formData.value = {
  1051. categoryIds: [],
  1052. categoryDisplayName: '',
  1053. merchantCategoryId: [],
  1054. merchantCategoryDisplayName: '',
  1055. tempName: '',
  1056. tempIds: [],
  1057. storeName: '',
  1058. keyword: '',
  1059. storeInfo: '',
  1060. unitName: '',
  1061. metalType: '',
  1062. weight: '',
  1063. laborCost: '',
  1064. additionalFee: '',
  1065. sort: '',
  1066. content: '',
  1067. stock: '',
  1068. barCode: ''
  1069. };
  1070. previewImages.value = [];
  1071. productImages.value = [];
  1072. productImagesGg.value = [];
  1073. descriptionText.value = '';
  1074. if (formRef.value) {
  1075. formRef.value.clearValidate();
  1076. }
  1077. }
  1078. // 规格管理相关
  1079. const editCatePage = () => {
  1080. if (attrTable.value && attrTable.value.length) {
  1081. historyAttr = []
  1082. historyTable = {}
  1083. attrTable.value.forEach(item => {
  1084. historyAttr.push(item.attrName)
  1085. historyTable[item.attrName] = {
  1086. ...item
  1087. }
  1088. })
  1089. }
  1090. uni.navigateTo({
  1091. url: "/pages/merchantCenters/productCate/productCate"
  1092. })
  1093. }
  1094. const setAllAttr = () => {
  1095. if (!validateAttr(oneTemplate.value)) return false;
  1096. uni.showModal({
  1097. title: '提示',
  1098. content: '所有商品规格将修改为当前配置',
  1099. confirmText: '确认',
  1100. cancelText: "取消",
  1101. success(res) {
  1102. if (res.confirm) {
  1103. if (attrTable.value && attrTable.value.length > 0) {
  1104. attrTable.value = attrTable.value.map(item => {
  1105. return {
  1106. ...item,
  1107. ...oneTemplate.value
  1108. }
  1109. })
  1110. }
  1111. }
  1112. }
  1113. })
  1114. }
  1115. // 规格格式化
  1116. function attrFormat(arr) {
  1117. let data = [];
  1118. const res = [];
  1119. return format(arr);
  1120. function format(arr) {
  1121. if (arr.length > 1) {
  1122. arr.forEach((v, i) => {
  1123. if (i === 0) data = arr[i]["attrValue"];
  1124. const tmp = [];
  1125. if (!data) return;
  1126. data.forEach(function(vv) {
  1127. arr[i + 1] &&
  1128. arr[i + 1]["attrValue"] &&
  1129. arr[i + 1]["attrValue"].forEach(g => {
  1130. const rep2 =
  1131. (i !== 0 ? "" : arr[i]["attrName"] + "_") +
  1132. vv +
  1133. "$&" +
  1134. arr[i + 1]["attrName"] +
  1135. "_" +
  1136. g;
  1137. tmp.push(rep2);
  1138. if (i === arr.length - 2) {
  1139. const rep4 = {
  1140. additionalAmount: "",
  1141. image: "",
  1142. price: "",
  1143. cost: 0,
  1144. otPrice: 0,
  1145. stock: "",
  1146. barCode: "",
  1147. weight: "",
  1148. volume: 0,
  1149. brokerage: 0,
  1150. brokerage_two: 0,
  1151. imgs: []
  1152. };
  1153. rep2.split("$&").forEach((h, k) => {
  1154. const rep3 = h.split("_");
  1155. if (!rep4["attrValue"]) rep4["attrValue"] = {};
  1156. rep4["attrValue"][rep3[0]] =
  1157. rep3.length > 1 ? rep3[1] : "";
  1158. });
  1159. for (const attrValueKey in rep4.attrValue) {
  1160. rep4[attrValueKey] = rep4.attrValue[attrValueKey];
  1161. }
  1162. res.push(rep4);
  1163. }
  1164. });
  1165. });
  1166. data = tmp.length ? tmp : [];
  1167. });
  1168. } else {
  1169. const dataArr = [];
  1170. arr.forEach((v, k) => {
  1171. v["attrValue"] &&
  1172. v["attrValue"].forEach((vv, kk) => {
  1173. dataArr[kk] = v["attrName"] + "_" + vv;
  1174. res[kk] = {
  1175. additionalAmount: "",
  1176. image: "",
  1177. price: "",
  1178. cost: 0,
  1179. otPrice: 0,
  1180. stock: "",
  1181. barCode: "",
  1182. weight: "",
  1183. volume: 0,
  1184. brokerage: 0,
  1185. brokerage_two: 0,
  1186. attrValue: {
  1187. [v["attrName"]]: vv
  1188. },
  1189. imgs: []
  1190. };
  1191. for (const attrValueKey in res[kk].attrValue) {
  1192. res[kk][attrValueKey] = res[kk].attrValue[attrValueKey];
  1193. }
  1194. });
  1195. });
  1196. data.push(dataArr.join("$&"));
  1197. }
  1198. const result = []
  1199. res.forEach(item => {
  1200. let attrName = [];
  1201. for (const name in item.attrValue) {
  1202. attrName.push(item.attrValue[name]);
  1203. }
  1204. item.attrName = attrName.join(',');
  1205. if (historyAttr.includes(item.attrName)) {
  1206. item = {
  1207. ...historyTable[item.attrName]
  1208. }
  1209. }
  1210. result.push(item)
  1211. })
  1212. return result;
  1213. }
  1214. }
  1215. // 输入框校验
  1216. const keyupEvent = (key, val, index, num) => {
  1217. if (key === "barCode") return;
  1218. var re = /^\D*([0-9]\d*\.?\d{0,2})?.*$/;
  1219. switch (num) {
  1220. case 1:
  1221. if (Number(val) === 0) {
  1222. oneTemplate.value[key] = [
  1223. "stock",
  1224. "cost",
  1225. "otPrice",
  1226. "price",
  1227. "additionalAmount"
  1228. ].includes(key) ?
  1229. 0 :
  1230. 0.01;
  1231. } else {
  1232. oneTemplate.value[key] =
  1233. key === "stock" ?
  1234. parseInt(val) :
  1235. val.toString().replace(re, "$1")
  1236. }
  1237. break;
  1238. case 2:
  1239. if (Number(val) === 0) {
  1240. attrTable.value[index][key] = [
  1241. "stock",
  1242. "cost",
  1243. "otPrice",
  1244. "price",
  1245. "additionalAmount"
  1246. ].includes(key) ?
  1247. 0 :
  1248. 0.01;
  1249. } else {
  1250. attrTable.value[index][key] =
  1251. key === "stock" ?
  1252. parseInt(val) :
  1253. val.toString().replace(re, "$1")
  1254. }
  1255. break;
  1256. default:
  1257. break
  1258. }
  1259. }
  1260. const formatterAttrValue = (attr) => {
  1261. // 如果属性名是"默认",直接返回"默认"
  1262. if (attr.attrName === "默认") {
  1263. return "默认";
  1264. }
  1265. try {
  1266. let data = attr.attrValue;
  1267. // 如果attrValue是字符串,尝试解析JSON
  1268. if (typeof data === 'string') {
  1269. data = JSON.parse(data);
  1270. }
  1271. // 如果解析后是对象,处理对象格式
  1272. if (typeof data === 'object' && data !== null) {
  1273. const result = [];
  1274. // 遍历所有属性
  1275. for (const [key, value] of Object.entries(data)) {
  1276. if (key === "单规格") {
  1277. // 如果是单规格,直接返回值
  1278. return value;
  1279. } else {
  1280. // 其他属性按 key:value 格式显示
  1281. result.push(`${key}:${value}`);
  1282. }
  1283. }
  1284. // 用逗号连接所有属性
  1285. return result.join(",");
  1286. } else {
  1287. // 如果不是对象,返回原始值
  1288. return typeof attr.attrValue === 'string' ? attr.attrValue : String(attr.attrValue);
  1289. }
  1290. } catch (error) {
  1291. // 如果解析失败,返回原始值
  1292. console.log("解析JSON失败,使用原始值:", attr.attrValue, error);
  1293. return typeof attr.attrValue === 'string' ? attr.attrValue : String(attr.attrValue);
  1294. }
  1295. };
  1296. </script>
  1297. <style scoped lang="scss">
  1298. .container {
  1299. background-color: #f9f7f0;
  1300. min-height: 100vh;
  1301. padding-bottom: 140rpx;
  1302. }
  1303. .form-container {
  1304. padding: 16rpx;
  1305. }
  1306. .upload-section {
  1307. margin-top: 20rpx;
  1308. margin-bottom: 20rpx;
  1309. background: white;
  1310. border-radius: 16rpx;
  1311. padding: 30rpx;
  1312. }
  1313. .upload-item {
  1314. margin-bottom: 40rpx;
  1315. &:last-child {
  1316. margin-bottom: 0;
  1317. }
  1318. }
  1319. .upload-label {
  1320. display: block;
  1321. font-size: 28rpx;
  1322. color: #333;
  1323. margin-bottom: 20rpx;
  1324. position: relative;
  1325. }
  1326. .required {
  1327. position: absolute;
  1328. left: -9px;
  1329. color: #f56c6c;
  1330. line-height: 20px;
  1331. font-size: 20px;
  1332. top: 3px;
  1333. }
  1334. .upload-btn {
  1335. display: flex;
  1336. flex-direction: column;
  1337. align-items: center;
  1338. justify-content: center;
  1339. width: 150rpx;
  1340. height: 150rpx;
  1341. border: 1rpx dashed #DCDFE6;
  1342. border-radius: 8rpx;
  1343. background: #fff;
  1344. }
  1345. .upload-tip {
  1346. font-size: 20rpx;
  1347. color: #666;
  1348. margin-top: 6rpx;
  1349. }
  1350. .format-tip {
  1351. display: block;
  1352. font-size: 24rpx;
  1353. color: #999;
  1354. margin-top: 15rpx;
  1355. }
  1356. .card-title {
  1357. font-size: 36rpx;
  1358. color: #333;
  1359. line-height: 60rpx;
  1360. margin-bottom: 20rpx;
  1361. }
  1362. ::v-deep .u-upload__wrap__preview__image{
  1363. width: 150rpx !important;
  1364. height: 150rpx !important;
  1365. }
  1366. :deep(.u-form-item) {
  1367. background-color: #fff;
  1368. border-radius: 16rpx;
  1369. padding: 8rpx 30rpx;
  1370. box-sizing: border-box;
  1371. margin-bottom: 20rpx;
  1372. }
  1373. :deep(.u-form-item__body__left__content__label) {
  1374. font-size: 28rpx !important;
  1375. color: #333 !important;
  1376. }
  1377. :deep(.u-radio-group--row) {
  1378. justify-content: flex-end;
  1379. }
  1380. .btn-view {
  1381. position: fixed;
  1382. bottom: 0;
  1383. left: 0;
  1384. right: 0;
  1385. background-color: #FFF;
  1386. padding: 20rpx 30rpx;
  1387. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
  1388. z-index: 5;
  1389. }
  1390. .submit {
  1391. height: 88rpx;
  1392. line-height: 88rpx;
  1393. background: #F8C008;
  1394. border-radius: 16rpx;
  1395. font-size: 32rpx;
  1396. font-weight: bold;
  1397. color: #333333;
  1398. border: none;
  1399. width: 100%;
  1400. &:active {
  1401. opacity: 0.8;
  1402. }
  1403. }
  1404. .unit {
  1405. color: #999;
  1406. font-size: 28rpx;
  1407. margin-left: 10rpx;
  1408. }
  1409. /* 编辑器样式 */
  1410. .editor-section {
  1411. background: white;
  1412. border-radius: 16rpx;
  1413. margin-bottom: 20rpx;
  1414. overflow: hidden;
  1415. }
  1416. .editor-header {
  1417. display: flex;
  1418. justify-content: space-between;
  1419. align-items: center;
  1420. padding: 30rpx;
  1421. border-bottom: 1rpx solid #f0f0f0;
  1422. }
  1423. .editor-label {
  1424. font-size: 28rpx;
  1425. color: #333;
  1426. font-weight: 500;
  1427. }
  1428. .word-count {
  1429. font-size: 24rpx;
  1430. color: #999;
  1431. }
  1432. :deep(.sp-editor) {
  1433. min-height: 400rpx;
  1434. }
  1435. /* 弹窗样式 */
  1436. .popup-content {
  1437. background: #fff;
  1438. border-radius: 20rpx 20rpx 0 0;
  1439. padding-bottom: env(safe-area-inset-bottom);
  1440. }
  1441. .popup-header {
  1442. padding: 30rpx;
  1443. text-align: center;
  1444. border-bottom: 1rpx solid #f0f0f0;
  1445. position: relative;
  1446. }
  1447. .popup-title {
  1448. font-size: 32rpx;
  1449. font-weight: 600;
  1450. color: #333;
  1451. }
  1452. .popup-actions {
  1453. display: flex;
  1454. padding: 30rpx;
  1455. gap: 20rpx;
  1456. }
  1457. .action-btn {
  1458. flex: 1;
  1459. height: 80rpx;
  1460. line-height: 80rpx;
  1461. border-radius: 12rpx;
  1462. font-size: 28rpx;
  1463. border: none;
  1464. &.cancel {
  1465. background: #f0f0f0;
  1466. color: #666;
  1467. }
  1468. &.confirm {
  1469. background: #F8C008;
  1470. color: #333;
  1471. }
  1472. }
  1473. :deep(.u-tabs__wrapper__nav__line) {
  1474. background-color: #F8C008 !important;
  1475. }
  1476. :deep(.u-form-item__body__right__message) {
  1477. text-align: right;
  1478. }
  1479. :deep(.u-textarea__field) {
  1480. text-align: right;
  1481. }
  1482. /* 规格样式 */
  1483. .pro-cate-container {
  1484. width: 100%;
  1485. padding: 16rpx;
  1486. background-color: #fff;
  1487. border-radius: 16rpx;
  1488. margin-bottom: 16rpx;
  1489. .cate-title {
  1490. font-size: 28rpx;
  1491. line-height: 44rpx;
  1492. color: #333;
  1493. margin-bottom: 16rpx;
  1494. }
  1495. .cate-add {
  1496. width: 100%;
  1497. height: 88rpx;
  1498. line-height: 88rpx;
  1499. text-align: center;
  1500. border-radius: 16rpx;
  1501. background-color: #FEF8E6;
  1502. color: #F8C008;
  1503. font-size: 32rpx;
  1504. font-weight: bold;
  1505. margin-top: 16rpx;
  1506. }
  1507. .cate-list {
  1508. .cate-item {
  1509. width: 100%;
  1510. background-color: #F9F7F0;
  1511. border-radius: 16rpx;
  1512. padding: 16rpx;
  1513. margin-bottom: 16rpx;
  1514. .upload-btn {
  1515. display: flex;
  1516. flex-direction: column;
  1517. align-items: center;
  1518. justify-content: center;
  1519. width: 100rpx;
  1520. height: 100rpx;
  1521. border: 1rpx dashed #DCDFE6;
  1522. border-radius: 8rpx;
  1523. background: #fff;
  1524. }
  1525. .cate-pic-line {
  1526. width: 100%;
  1527. height: 100rpx;
  1528. display: flex;
  1529. justify-content: space-between;
  1530. align-items: center;
  1531. margin-bottom: 16rpx;
  1532. ::v-deep .u-upload {
  1533. flex: 0;
  1534. margin-right: 16rpx;
  1535. width: 100rpx;
  1536. height: 100rpx;
  1537. }
  1538. ::v-deep .u-upload__wrap__preview {
  1539. margin: 0;
  1540. width: 100rpx;
  1541. height: 100rpx;
  1542. }
  1543. ::v-deep .u-upload__wrap__preview__image {
  1544. width: 100rpx !important;
  1545. height: 100rpx !important;
  1546. }
  1547. .cate-name {
  1548. width: 100rpx;
  1549. flex: 1;
  1550. color: #333;
  1551. font-size: 28rpx;
  1552. line-height: 44rpx;
  1553. }
  1554. }
  1555. .cate-ipt-line {
  1556. width: 100%;
  1557. display: flex;
  1558. align-items: center;
  1559. justify-content: space-between;
  1560. flex-wrap: nowrap;
  1561. padding-bottom: 16rpx;
  1562. ::v-deep .u-input {
  1563. height: 100rpx;
  1564. line-height: 100rpx;
  1565. }
  1566. .cate-ipt {
  1567. width: 48%;
  1568. flex: 1;
  1569. height: 100rpx;
  1570. background-color: #fff;
  1571. border-radius: 16rpx;
  1572. padding: 0 16rpx !important;
  1573. font-size: 28rpx;
  1574. .unit {
  1575. color: #333;
  1576. }
  1577. }
  1578. }
  1579. }
  1580. }
  1581. }
  1582. </style>