releaseProduct.vue 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738
  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. const table = attrFormat(data)
  524. attr.value = data.concat();
  525. attrTable.value = table
  526. }
  527. // 获取商品分类
  528. async function getProductCategory() {
  529. let obj = {
  530. type: 1,
  531. status: 1
  532. }
  533. try {
  534. const {
  535. data
  536. } = await productCategory(obj)
  537. console.log('原始分类数据:', data);
  538. const newArr = []
  539. data.forEach((value, index) => {
  540. newArr[index] = value
  541. if (value.child) newArr[index].child = value.child.filter(item => item.status === true)
  542. })
  543. categoryData.value = newArr.filter(item => item.code !== 'bb_mall')
  544. console.log('转换后的分类数据:', categoryData.value);
  545. } catch (error) {
  546. console.error('获取商品分类失败:', error);
  547. uni.showToast({
  548. title: '获取分类失败',
  549. icon: 'none'
  550. });
  551. }
  552. }
  553. // 获取商品类目列表
  554. async function getMerchantCategoryList(){
  555. try {
  556. const { data } = await getMerchantCategory();
  557. console.log('原始商品类目数据:', data);
  558. merchantCategoryData.value = data.map(item => ({
  559. id: item.id,
  560. name: item.categoryName,
  561. child: []
  562. }));
  563. console.log('适配后的商品类目数据:', merchantCategoryData.value);
  564. } catch (error) {
  565. console.error('获取商品类目失败:', error);
  566. uni.showToast({ title: '获取商品类目失败', icon: 'none' });
  567. }
  568. }
  569. // 获取运费模板
  570. async function getTempData() {
  571. let obj = {
  572. page: 1,
  573. limit: 9999
  574. }
  575. try {
  576. const {
  577. data
  578. } = await templatesList()
  579. tempColumns.value[0] = data.list;
  580. } catch (error) {
  581. console.error('获取运费模板失败:', error);
  582. uni.showToast({
  583. title: '获取运费模板失败',
  584. icon: 'none'
  585. });
  586. }
  587. }
  588. // 初始化表单验证
  589. const initFormValidation = async () => {
  590. await nextTick();
  591. if (!formRef.value) {
  592. console.error('表单引用不存在');
  593. return;
  594. }
  595. try {
  596. formRef.value.clearValidate();
  597. setTimeout(async () => {
  598. const requiredFields = ['categoryIds', 'merchantCategoryId', 'storeName', 'keyword', 'storeInfo', 'unitName',
  599. 'tempIds', 'metalType'
  600. ];
  601. for (const field of requiredFields) {
  602. try {
  603. await formRef.value.validateField(field);
  604. } catch (error) {
  605. console.log(`⚠️ 字段 ${field} 验证状态:`, error);
  606. }
  607. }
  608. }, 300);
  609. } catch (error) {
  610. console.error('初始化表单验证状态失败:', error);
  611. }
  612. };
  613. // 获取商品详情
  614. async function getProductDetail(id) {
  615. try {
  616. const {
  617. data
  618. } = await productInfo(id)
  619. console.log('获取商品详情:', data);
  620. Object.assign(formData.value, {
  621. ...data,
  622. categoryIds: data.cateId ? (Array.isArray(data.cateId) ? data.cateId : data.cateId.split(',')) : [],
  623. merchantCategoryId: data.merchantCategoryId ? (Array.isArray(data.merchantCategoryId) ? data.merchantCategoryId : [data.merchantCategoryId]) : [],
  624. tempIds: data.tempId ? [data.tempId] : []
  625. });
  626. productImages.value = [];
  627. console.log('分类IDs:', formData.value.categoryIds);
  628. console.log('商品类目IDs:', formData.value.merchantCategoryId);
  629. console.log('运费模板IDs:', formData.value.tempIds);
  630. await nextTick();
  631. formData.value.categoryDisplayName = getCategoryDisplayName(formData.value.categoryIds);
  632. formData.value.merchantCategoryDisplayName = getMerchantCategoryDisplayName(formData.value.merchantCategoryId);
  633. formData.value.tempName = formatterTemp(data.tempId);
  634. // 图片处理
  635. previewImages.value = data.image ? [{
  636. url: data.image
  637. }] : [];
  638. // 商品轮播图
  639. if (data.sliderImage) {
  640. try {
  641. const urlArr = typeof data.sliderImage === 'string' ? JSON.parse(data.sliderImage) : data.sliderImage;
  642. if (urlArr && urlArr.length > 0) {
  643. productImages.value = urlArr.map(url => ({
  644. url
  645. }));
  646. }
  647. } catch (error) {
  648. console.error('解析轮播图失败:', error);
  649. productImages.value = [];
  650. }
  651. }
  652. // 商品属性
  653. if (data.attrValue && data.attrValue.length > 0) {
  654. data.attr.forEach(item => {
  655. attr.value.push({
  656. attrName: item.attrName,
  657. attrValue: item.attrValues.split(',')
  658. })
  659. })
  660. data.attrValue.forEach(item => {
  661. let attrName = item.suk;
  662. attrTable.value.push({
  663. ...item,
  664. attrName: attrName,
  665. imgs: item?.image ? [{
  666. url: item.image
  667. }] : []
  668. })
  669. })
  670. appStore.SET_CPATTR(attr.value)
  671. }
  672. // 商品描述
  673. descriptionText.value = data.content ? data.content.replace(/<[^>]*>/g, '').substring(0, 500) : '';
  674. formData.value.content = data.content || '';
  675. // 设置分类选择器
  676. if (categoryRef.value && formData.value.categoryIds.length > 0) {
  677. await nextTick();
  678. if (categoryRef.value.setSelectedIds) {
  679. categoryRef.value.setSelectedIds(formData.value.categoryIds);
  680. }
  681. }
  682. // 设置商品类目选择器
  683. if (merchantCategoryRef.value && formData.value.merchantCategoryId.length > 0) {
  684. await nextTick();
  685. if (merchantCategoryRef.value.setSelectedIds) {
  686. merchantCategoryRef.value.setSelectedIds(formData.value.merchantCategoryId);
  687. }
  688. }
  689. await initFormValidation();
  690. } catch (error) {
  691. console.error('获取商品详情失败:', error);
  692. uni.showToast({
  693. title: '获取商品详情失败',
  694. icon: 'none'
  695. });
  696. }
  697. }
  698. // 运费模板id转名称
  699. function formatterTemp(id) {
  700. const foundItem = tempColumns.value[0]?.find(item => item.id == id);
  701. return foundItem ? foundItem.name : '';
  702. }
  703. // 运费模板确认选择
  704. function tempConfirm(obj) {
  705. formData.value.tempId = obj.value[0].id;
  706. formData.value.tempName = obj.value[0].name;
  707. showTemp.value = false;
  708. setTimeout(() => {
  709. if (formRef.value) {
  710. formRef.value.validateField('tempIds');
  711. }
  712. }, 100);
  713. }
  714. // 分类选择变化
  715. const onCategoryChange = (result) => {
  716. console.log('分类选择变化:', result);
  717. }
  718. // 商品类目选择变化
  719. const onMerchantCategoryChange = (result) => {
  720. console.log('商品类目选择变化:', result);
  721. }
  722. // 确认分类选择
  723. const confirmCategory = () => {
  724. if (categoryRef.value) {
  725. const selectedIds = categoryRef.value.getSelectedIds()
  726. if (selectedIds.length === 0) {
  727. uni.showToast({
  728. title: '请至少选择一个分类',
  729. icon: 'none'
  730. })
  731. return
  732. }
  733. formData.value.categoryIds = selectedIds
  734. formData.value.categoryDisplayName = getCategoryDisplayName(selectedIds)
  735. showCategory.value = false
  736. console.log('最终选中的分类ID:', selectedIds)
  737. setTimeout(() => {
  738. if (formRef.value) {
  739. formRef.value.validateField('categoryIds');
  740. }
  741. }, 100);
  742. }
  743. }
  744. // 确认商品类目选择
  745. const confirmMerchantCategory = () => {
  746. if (merchantCategoryRef.value) {
  747. let selectedIds = merchantCategoryRef.value.getSelectedIds()
  748. if (selectedIds.length === 0) {
  749. uni.showToast({ title: '请选择一个商品类目', icon: 'none' })
  750. return
  751. }
  752. if (selectedIds.length > 1) {
  753. selectedIds = [selectedIds[0]]
  754. uni.showToast({ title: '商品类目仅支持选择一个', icon: 'none' })
  755. }
  756. formData.value.merchantCategoryId = selectedIds
  757. formData.value.merchantCategoryDisplayName = getMerchantCategoryDisplayName(selectedIds)
  758. showMerchantCategory.value = false
  759. console.log('最终选中的商品类目ID(单选):', selectedIds)
  760. setTimeout(() => {
  761. if (formRef.value) {
  762. formRef.value.validateField('merchantCategoryId');
  763. }
  764. }, 100);
  765. }
  766. }
  767. // 生成分类显示名称
  768. const getCategoryDisplayName = (selectedIds) => {
  769. if (!selectedIds || selectedIds.length === 0) return ''
  770. const names = []
  771. selectedIds.forEach(id => {
  772. const firstLevel = categoryData.value.find(item => item.id == id)
  773. if (firstLevel) {
  774. names.push(`${firstLevel.name}`)
  775. } else {
  776. for (const parent of categoryData.value) {
  777. if (parent.child) {
  778. const secondLevel = parent.child.find(child => child.id == id)
  779. if (secondLevel) {
  780. names.push(`${parent.name}-${secondLevel.name}`)
  781. break
  782. }
  783. }
  784. }
  785. const singleLevel = categoryData.value.find(item =>
  786. !item.child && item.id == id
  787. )
  788. if (singleLevel) {
  789. names.push(singleLevel.name)
  790. }
  791. }
  792. })
  793. return names.join('、')
  794. }
  795. // 生成商品类目显示名称
  796. const getMerchantCategoryDisplayName = (selectedIds) => {
  797. if (!selectedIds || selectedIds.length === 0) return ''
  798. const firstId = selectedIds[0]
  799. const category = merchantCategoryData.value.find(item => item.id == firstId)
  800. return category ? category.name : ''
  801. }
  802. // 图片上传相关
  803. async function getImage() {
  804. console.log(imageList.value)
  805. if (imageList.value.length > 0) {
  806. if (imageList.value[0].status == "success") {
  807. previewImages.value = imageList.value;
  808. } else {
  809. Toast({
  810. title: "上传失败"
  811. });
  812. }
  813. }
  814. imageList.value = [];
  815. }
  816. async function getImageProduct() {
  817. if (imageList.value.length > 0) {
  818. if (imageList.value[0].status == "success") {
  819. productImages.value = [...productImages.value, ...imageList.value];
  820. } else {
  821. Toast({
  822. title: "上传失败"
  823. });
  824. }
  825. }
  826. imageList.value = [];
  827. }
  828. async function getImageProductGg(index) {
  829. if (imageList.value.length > 0) {
  830. if (imageList.value[0].status == "success") {
  831. if (index == "oneTemplate") {
  832. oneTemplate.value.imgs = imageList.value
  833. oneTemplate.value.image = imageList.value[0].info.url
  834. } else {
  835. attrTable.value[index].imgs = imageList.value
  836. attrTable.value[index].image = imageList.value[0].info.url
  837. }
  838. } else {
  839. Toast({
  840. title: "上传失败"
  841. });
  842. }
  843. }
  844. imageList.value = [];
  845. }
  846. const onPreviewImageDelete = (e) => {
  847. previewImages.value.splice(e.index, 1);
  848. };
  849. const onProductImageDelete = (e) => {
  850. productImages.value.splice(e.index, 1);
  851. };
  852. const onProductImageGgDelete = (e) => {
  853. if (e == "oneTemplate") {
  854. oneTemplate.value.imgs = []
  855. } else {
  856. attrTable.value[e].imgs = []
  857. }
  858. productImagesGg.value.splice(e.index, 1);
  859. };
  860. // 编辑器相关方法
  861. const onEditorInput = (e) => {
  862. descriptionText.value = e.text || '';
  863. formData.value.content = e.html || '';
  864. };
  865. const onEditorInit = (editor) => {
  866. editorIns.value = editor;
  867. };
  868. const onOverMax = (e) => {
  869. uni.showToast({
  870. title: '内容长度超出限制',
  871. icon: 'none'
  872. });
  873. };
  874. const onUpinImage = (tempFiles, editorCtx) => {
  875. const filePath = tempFiles[0].tempFilePath || tempFiles[0].path;
  876. editorCtx.insertImage({
  877. src: filePath,
  878. width: '80%',
  879. success: () => {
  880. uni.showToast({
  881. title: '图片插入成功',
  882. icon: 'success'
  883. });
  884. }
  885. });
  886. };
  887. // 表单验证
  888. const validateForm = () => {
  889. try {
  890. if (!isProductCenter.value) {
  891. if (previewImages.value.length === 0) {
  892. uni.showToast({
  893. title: '请上传商品预览图',
  894. icon: 'none'
  895. });
  896. return;
  897. }
  898. if (productImages.value.length === 0) {
  899. uni.showToast({
  900. title: '请上传商品图片',
  901. icon: 'none'
  902. });
  903. return;
  904. }
  905. if (formData.value.sort && !/^\d+$/.test(formData.value.sort)) {
  906. uni.showToast({
  907. title: '排序号必须是整数',
  908. icon: 'none'
  909. });
  910. return;
  911. }
  912. }
  913. const valid = formRef.value.validate();
  914. return valid;
  915. } catch (error) {
  916. console.error('表单验证失败:', error);
  917. return false;
  918. }
  919. };
  920. // 规格验证
  921. const validateAttr = (item) => {
  922. if (!item.image) {
  923. uni.showToast({
  924. title: '请上传商品规格图片',
  925. icon: 'none'
  926. });
  927. return false;
  928. }
  929. if (!/^\d+(\.\d+)?$/.test(item.weight)) {
  930. uni.showToast({
  931. title: '重量格式不正确',
  932. icon: 'none'
  933. });
  934. return false;
  935. }
  936. if (!/^\d+(\.\d+)?$/.test(item.price)) {
  937. uni.showToast({
  938. title: '工费格式不正确',
  939. icon: 'none'
  940. });
  941. return false;
  942. }
  943. if (!/^\d+(\.\d+)?$/.test(item.additionalAmount)) {
  944. uni.showToast({
  945. title: '附加费格式不正确',
  946. icon: 'none'
  947. });
  948. return false;
  949. }
  950. if (!/^\d+$/.test(item.stock)) {
  951. uni.showToast({
  952. title: '库存必须是整数',
  953. icon: 'none'
  954. });
  955. return false;
  956. }
  957. if (!/^\d+$/.test(item.barCode)) {
  958. uni.showToast({
  959. title: '商品编号必须是整数',
  960. icon: 'none'
  961. });
  962. return false;
  963. }
  964. return true
  965. }
  966. // 提交表单
  967. const submitForm = async () => {
  968. const valid = validateForm();
  969. if (valid) {
  970. const submitData = {
  971. ...formData.value
  972. };
  973. let urlString = '';
  974. if (isProductCenter.value) {
  975. submitData.image = previewImages.value[0].url;
  976. urlString = JSON.stringify(productImages.value.map(item => item.url));
  977. } else {
  978. submitData.image = previewImages.value[0]?.info?.url || previewImages.value[0]?.url;
  979. urlString = JSON.stringify(productImages.value.map(item => item?.info?.url || item?.url));
  980. }
  981. submitData.sliderImage = urlString;
  982. submitData.cateId = formData.value.categoryIds.join(',');
  983. submitData.merchantCategoryId = formData.value.merchantCategoryId;
  984. submitData.merchantId = parseInt(merchantInfo.id);
  985. submitData.specType = true;
  986. submitData.isSub = false;
  987. if (!attr.value || attr.value.length <= 0 || !attrTable.value || attrTable.value.length <= 0) {
  988. return Toast({
  989. title: "请添加商品规格!"
  990. });
  991. }
  992. let pass = true;
  993. submitData.attr = attr.value.map(item => {
  994. return {
  995. attrName: item.attrName,
  996. attrValues: item.attrValue.join(','),
  997. }
  998. })
  999. submitData.attrValue = attrTable.value.map(item => {
  1000. if (!validateAttr(item)) {
  1001. pass = false;
  1002. }
  1003. return {
  1004. ...item,
  1005. id: 0,
  1006. productId: 0,
  1007. attrValue: typeof(item.attrValue) == 'object' ? JSON.stringify(item.attrValue) : item.attrValue,
  1008. image: item.image || item.imgs[0]?.info?.url || item.imgs[0]?.url
  1009. }
  1010. })
  1011. if (!pass) return false;
  1012. if (productId.value && !isProductCenter.value) {
  1013. const {
  1014. data
  1015. } = await productUpdate(submitData);
  1016. uni.showToast({
  1017. title: '修改成功',
  1018. icon: 'success'
  1019. });
  1020. } else {
  1021. const {
  1022. data
  1023. } = await productSave(submitData);
  1024. uni.showToast({
  1025. title: '发布成功',
  1026. icon: 'success'
  1027. });
  1028. }
  1029. if (!!productId.value && !isProductCenter.value) {
  1030. uni.navigateBack()
  1031. } else {
  1032. uni.redirectTo({
  1033. url: '/pages/merchantCenters/productManagement'
  1034. })
  1035. }
  1036. }
  1037. }
  1038. // 重置表单
  1039. const resetForm = () => {
  1040. formData.value = {
  1041. categoryIds: [],
  1042. categoryDisplayName: '',
  1043. merchantCategoryId: [],
  1044. merchantCategoryDisplayName: '',
  1045. tempName: '',
  1046. tempIds: [],
  1047. storeName: '',
  1048. keyword: '',
  1049. storeInfo: '',
  1050. unitName: '',
  1051. metalType: '',
  1052. weight: '',
  1053. laborCost: '',
  1054. additionalFee: '',
  1055. sort: '',
  1056. content: '',
  1057. stock: '',
  1058. barCode: ''
  1059. };
  1060. previewImages.value = [];
  1061. productImages.value = [];
  1062. productImagesGg.value = [];
  1063. descriptionText.value = '';
  1064. if (formRef.value) {
  1065. formRef.value.clearValidate();
  1066. }
  1067. }
  1068. // 规格管理相关
  1069. const editCatePage = () => {
  1070. if (attrTable.value && attrTable.value.length) {
  1071. historyAttr = []
  1072. historyTable = {}
  1073. attrTable.value.forEach(item => {
  1074. historyAttr.push(item.attrName)
  1075. historyTable[item.attrName] = {
  1076. ...item
  1077. }
  1078. })
  1079. }
  1080. uni.navigateTo({
  1081. url: "/pages/merchantCenters/productCate/productCate"
  1082. })
  1083. }
  1084. const setAllAttr = () => {
  1085. if (!validateAttr(oneTemplate.value)) return false;
  1086. uni.showModal({
  1087. title: '提示',
  1088. content: '所有商品规格将修改为当前配置',
  1089. confirmText: '确认',
  1090. cancelText: "取消",
  1091. success(res) {
  1092. if (res.confirm) {
  1093. if (attrTable.value && attrTable.value.length > 0) {
  1094. attrTable.value = attrTable.value.map(item => {
  1095. return {
  1096. ...item,
  1097. ...oneTemplate.value
  1098. }
  1099. })
  1100. }
  1101. }
  1102. }
  1103. })
  1104. }
  1105. // 规格格式化
  1106. function attrFormat(arr) {
  1107. let data = [];
  1108. const res = [];
  1109. return format(arr);
  1110. function format(arr) {
  1111. if (arr.length > 1) {
  1112. arr.forEach((v, i) => {
  1113. if (i === 0) data = arr[i]["attrValue"];
  1114. const tmp = [];
  1115. if (!data) return;
  1116. data.forEach(function(vv) {
  1117. arr[i + 1] &&
  1118. arr[i + 1]["attrValue"] &&
  1119. arr[i + 1]["attrValue"].forEach(g => {
  1120. const rep2 =
  1121. (i !== 0 ? "" : arr[i]["attrName"] + "_") +
  1122. vv +
  1123. "$&" +
  1124. arr[i + 1]["attrName"] +
  1125. "_" +
  1126. g;
  1127. tmp.push(rep2);
  1128. if (i === arr.length - 2) {
  1129. const rep4 = {
  1130. additionalAmount: "",
  1131. image: "",
  1132. price: "",
  1133. cost: 0,
  1134. otPrice: 0,
  1135. stock: "",
  1136. barCode: "",
  1137. weight: "",
  1138. volume: 0,
  1139. brokerage: 0,
  1140. brokerage_two: 0,
  1141. imgs: []
  1142. };
  1143. rep2.split("$&").forEach((h, k) => {
  1144. const rep3 = h.split("_");
  1145. if (!rep4["attrValue"]) rep4["attrValue"] = {};
  1146. rep4["attrValue"][rep3[0]] =
  1147. rep3.length > 1 ? rep3[1] : "";
  1148. });
  1149. for (const attrValueKey in rep4.attrValue) {
  1150. rep4[attrValueKey] = rep4.attrValue[attrValueKey];
  1151. }
  1152. res.push(rep4);
  1153. }
  1154. });
  1155. });
  1156. data = tmp.length ? tmp : [];
  1157. });
  1158. } else {
  1159. const dataArr = [];
  1160. arr.forEach((v, k) => {
  1161. v["attrValue"] &&
  1162. v["attrValue"].forEach((vv, kk) => {
  1163. dataArr[kk] = v["attrName"] + "_" + vv;
  1164. res[kk] = {
  1165. additionalAmount: "",
  1166. image: "",
  1167. price: "",
  1168. cost: 0,
  1169. otPrice: 0,
  1170. stock: "",
  1171. barCode: "",
  1172. weight: "",
  1173. volume: 0,
  1174. brokerage: 0,
  1175. brokerage_two: 0,
  1176. attrValue: {
  1177. [v["attrName"]]: vv
  1178. },
  1179. imgs: []
  1180. };
  1181. for (const attrValueKey in res[kk].attrValue) {
  1182. res[kk][attrValueKey] = res[kk].attrValue[attrValueKey];
  1183. }
  1184. });
  1185. });
  1186. data.push(dataArr.join("$&"));
  1187. }
  1188. const result = []
  1189. res.forEach(item => {
  1190. let attrName = [];
  1191. for (const name in item.attrValue) {
  1192. attrName.push(item.attrValue[name]);
  1193. }
  1194. item.attrName = attrName.join(',');
  1195. if (historyAttr.includes(item.attrName)) {
  1196. item = {
  1197. ...historyTable[item.attrName]
  1198. }
  1199. }
  1200. result.push(item)
  1201. })
  1202. return result;
  1203. }
  1204. }
  1205. // 输入框校验
  1206. const keyupEvent = (key, val, index, num) => {
  1207. if (key === "barCode") return;
  1208. var re = /^\D*([0-9]\d*\.?\d{0,2})?.*$/;
  1209. switch (num) {
  1210. case 1:
  1211. if (Number(val) === 0) {
  1212. oneTemplate.value[key] = [
  1213. "stock",
  1214. "cost",
  1215. "otPrice",
  1216. "price",
  1217. "additionalAmount"
  1218. ].includes(key) ?
  1219. 0 :
  1220. 0.01;
  1221. } else {
  1222. oneTemplate.value[key] =
  1223. key === "stock" ?
  1224. parseInt(val) :
  1225. val.toString().replace(re, "$1")
  1226. }
  1227. break;
  1228. case 2:
  1229. if (Number(val) === 0) {
  1230. attrTable.value[index][key] = [
  1231. "stock",
  1232. "cost",
  1233. "otPrice",
  1234. "price",
  1235. "additionalAmount"
  1236. ].includes(key) ?
  1237. 0 :
  1238. 0.01;
  1239. } else {
  1240. attrTable.value[index][key] =
  1241. key === "stock" ?
  1242. parseInt(val) :
  1243. val.toString().replace(re, "$1")
  1244. }
  1245. break;
  1246. default:
  1247. break
  1248. }
  1249. }
  1250. const formatterAttrValue = (attr) => {
  1251. // 如果属性名是"默认",直接返回"默认"
  1252. if (attr.attrName === "默认") {
  1253. return "默认";
  1254. }
  1255. try {
  1256. // 尝试解析JSON
  1257. const data = JSON.parse(attr.attrValue);
  1258. const result = [];
  1259. // 遍历所有属性
  1260. for (const [key, value] of Object.entries(data)) {
  1261. if (key === "单规格") {
  1262. // 如果是单规格,直接返回值
  1263. return value;
  1264. } else if (key === "重量" || key === "重量(克)" || key.endsWith("重量")) {
  1265. // 如果属性名包含"重量",添加单位g
  1266. result.push(`${key}:${value}g`);
  1267. } else {
  1268. // 圈号直接显示
  1269. result.push(`${key}:${value}`);
  1270. }
  1271. }
  1272. // 用逗号连接所有属性
  1273. return result.join(",");
  1274. } catch (error) {
  1275. // 如果不是有效的JSON,返回原始值
  1276. console.error("解析JSON失败:", error);
  1277. return attr.attrValue;
  1278. }
  1279. };
  1280. </script>
  1281. <style scoped lang="scss">
  1282. .container {
  1283. background-color: #f9f7f0;
  1284. min-height: 100vh;
  1285. padding-bottom: 140rpx;
  1286. }
  1287. .form-container {
  1288. padding: 16rpx;
  1289. }
  1290. .upload-section {
  1291. margin-top: 20rpx;
  1292. margin-bottom: 20rpx;
  1293. background: white;
  1294. border-radius: 16rpx;
  1295. padding: 30rpx;
  1296. }
  1297. .upload-item {
  1298. margin-bottom: 40rpx;
  1299. &:last-child {
  1300. margin-bottom: 0;
  1301. }
  1302. }
  1303. .upload-label {
  1304. display: block;
  1305. font-size: 28rpx;
  1306. color: #333;
  1307. margin-bottom: 20rpx;
  1308. position: relative;
  1309. }
  1310. .required {
  1311. position: absolute;
  1312. left: -9px;
  1313. color: #f56c6c;
  1314. line-height: 20px;
  1315. font-size: 20px;
  1316. top: 3px;
  1317. }
  1318. .upload-btn {
  1319. display: flex;
  1320. flex-direction: column;
  1321. align-items: center;
  1322. justify-content: center;
  1323. width: 150rpx;
  1324. height: 150rpx;
  1325. border: 1rpx dashed #DCDFE6;
  1326. border-radius: 8rpx;
  1327. background: #fff;
  1328. }
  1329. .upload-tip {
  1330. font-size: 20rpx;
  1331. color: #666;
  1332. margin-top: 6rpx;
  1333. }
  1334. .format-tip {
  1335. display: block;
  1336. font-size: 24rpx;
  1337. color: #999;
  1338. margin-top: 15rpx;
  1339. }
  1340. .card-title {
  1341. font-size: 36rpx;
  1342. color: #333;
  1343. line-height: 60rpx;
  1344. margin-bottom: 20rpx;
  1345. }
  1346. ::v-deep .u-upload__wrap__preview__image{
  1347. width: 150rpx !important;
  1348. height: 150rpx !important;
  1349. }
  1350. :deep(.u-form-item) {
  1351. background-color: #fff;
  1352. border-radius: 16rpx;
  1353. padding: 8rpx 30rpx;
  1354. box-sizing: border-box;
  1355. margin-bottom: 20rpx;
  1356. }
  1357. :deep(.u-form-item__body__left__content__label) {
  1358. font-size: 28rpx !important;
  1359. color: #333 !important;
  1360. }
  1361. :deep(.u-radio-group--row) {
  1362. justify-content: flex-end;
  1363. }
  1364. .btn-view {
  1365. position: fixed;
  1366. bottom: 0;
  1367. left: 0;
  1368. right: 0;
  1369. background-color: #FFF;
  1370. padding: 20rpx 30rpx;
  1371. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
  1372. z-index: 5;
  1373. }
  1374. .submit {
  1375. height: 88rpx;
  1376. line-height: 88rpx;
  1377. background: #F8C008;
  1378. border-radius: 16rpx;
  1379. font-size: 32rpx;
  1380. font-weight: bold;
  1381. color: #333333;
  1382. border: none;
  1383. width: 100%;
  1384. &:active {
  1385. opacity: 0.8;
  1386. }
  1387. }
  1388. .unit {
  1389. color: #999;
  1390. font-size: 28rpx;
  1391. margin-left: 10rpx;
  1392. }
  1393. /* 编辑器样式 */
  1394. .editor-section {
  1395. background: white;
  1396. border-radius: 16rpx;
  1397. margin-bottom: 20rpx;
  1398. overflow: hidden;
  1399. }
  1400. .editor-header {
  1401. display: flex;
  1402. justify-content: space-between;
  1403. align-items: center;
  1404. padding: 30rpx;
  1405. border-bottom: 1rpx solid #f0f0f0;
  1406. }
  1407. .editor-label {
  1408. font-size: 28rpx;
  1409. color: #333;
  1410. font-weight: 500;
  1411. }
  1412. .word-count {
  1413. font-size: 24rpx;
  1414. color: #999;
  1415. }
  1416. :deep(.sp-editor) {
  1417. min-height: 400rpx;
  1418. }
  1419. /* 弹窗样式 */
  1420. .popup-content {
  1421. background: #fff;
  1422. border-radius: 20rpx 20rpx 0 0;
  1423. padding-bottom: env(safe-area-inset-bottom);
  1424. }
  1425. .popup-header {
  1426. padding: 30rpx;
  1427. text-align: center;
  1428. border-bottom: 1rpx solid #f0f0f0;
  1429. position: relative;
  1430. }
  1431. .popup-title {
  1432. font-size: 32rpx;
  1433. font-weight: 600;
  1434. color: #333;
  1435. }
  1436. .popup-actions {
  1437. display: flex;
  1438. padding: 30rpx;
  1439. gap: 20rpx;
  1440. }
  1441. .action-btn {
  1442. flex: 1;
  1443. height: 80rpx;
  1444. line-height: 80rpx;
  1445. border-radius: 12rpx;
  1446. font-size: 28rpx;
  1447. border: none;
  1448. &.cancel {
  1449. background: #f0f0f0;
  1450. color: #666;
  1451. }
  1452. &.confirm {
  1453. background: #F8C008;
  1454. color: #333;
  1455. }
  1456. }
  1457. :deep(.u-tabs__wrapper__nav__line) {
  1458. background-color: #F8C008 !important;
  1459. }
  1460. :deep(.u-form-item__body__right__message) {
  1461. text-align: right;
  1462. }
  1463. :deep(.u-textarea__field) {
  1464. text-align: right;
  1465. }
  1466. /* 规格样式 */
  1467. .pro-cate-container {
  1468. width: 100%;
  1469. padding: 16rpx;
  1470. background-color: #fff;
  1471. border-radius: 16rpx;
  1472. margin-bottom: 16rpx;
  1473. .cate-title {
  1474. font-size: 28rpx;
  1475. line-height: 44rpx;
  1476. color: #333;
  1477. margin-bottom: 16rpx;
  1478. }
  1479. .cate-add {
  1480. width: 100%;
  1481. height: 88rpx;
  1482. line-height: 88rpx;
  1483. text-align: center;
  1484. border-radius: 16rpx;
  1485. background-color: #FEF8E6;
  1486. color: #F8C008;
  1487. font-size: 32rpx;
  1488. font-weight: bold;
  1489. margin-top: 16rpx;
  1490. }
  1491. .cate-list {
  1492. .cate-item {
  1493. width: 100%;
  1494. background-color: #F9F7F0;
  1495. border-radius: 16rpx;
  1496. padding: 16rpx;
  1497. margin-bottom: 16rpx;
  1498. .upload-btn {
  1499. display: flex;
  1500. flex-direction: column;
  1501. align-items: center;
  1502. justify-content: center;
  1503. width: 100rpx;
  1504. height: 100rpx;
  1505. border: 1rpx dashed #DCDFE6;
  1506. border-radius: 8rpx;
  1507. background: #fff;
  1508. }
  1509. .cate-pic-line {
  1510. width: 100%;
  1511. height: 100rpx;
  1512. display: flex;
  1513. justify-content: space-between;
  1514. align-items: center;
  1515. margin-bottom: 16rpx;
  1516. ::v-deep .u-upload {
  1517. flex: 0;
  1518. margin-right: 16rpx;
  1519. width: 100rpx;
  1520. height: 100rpx;
  1521. }
  1522. ::v-deep .u-upload__wrap__preview {
  1523. margin: 0;
  1524. width: 100rpx;
  1525. height: 100rpx;
  1526. }
  1527. ::v-deep .u-upload__wrap__preview__image {
  1528. width: 100rpx !important;
  1529. height: 100rpx !important;
  1530. }
  1531. .cate-name {
  1532. width: 100rpx;
  1533. flex: 1;
  1534. color: #333;
  1535. font-size: 28rpx;
  1536. line-height: 44rpx;
  1537. }
  1538. }
  1539. .cate-ipt-line {
  1540. width: 100%;
  1541. display: flex;
  1542. align-items: center;
  1543. justify-content: space-between;
  1544. flex-wrap: nowrap;
  1545. padding-bottom: 16rpx;
  1546. ::v-deep .u-input {
  1547. height: 100rpx;
  1548. line-height: 100rpx;
  1549. }
  1550. .cate-ipt {
  1551. width: 48%;
  1552. flex: 1;
  1553. height: 100rpx;
  1554. background-color: #fff;
  1555. border-radius: 16rpx;
  1556. padding: 0 16rpx !important;
  1557. font-size: 28rpx;
  1558. .unit {
  1559. color: #333;
  1560. }
  1561. }
  1562. }
  1563. }
  1564. }
  1565. }
  1566. </style>