releaseProduct.vue 43 KB

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