releaseProduct.vue 45 KB

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