releaseProduct.vue 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695
  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.attrValue)}}
  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 = (attrstrValue) => {
  1238. const data = JSON.parse(attrstrValue);
  1239. const result = `圈号:${data.圈号},重量:${data.重量}g`;
  1240. return result;
  1241. }
  1242. </script>
  1243. <style scoped lang="scss">
  1244. .container {
  1245. background-color: #f9f7f0;
  1246. min-height: 100vh;
  1247. padding-bottom: 140rpx;
  1248. }
  1249. .form-container {
  1250. padding: 16rpx;
  1251. }
  1252. .upload-section {
  1253. margin-top: 20rpx;
  1254. margin-bottom: 20rpx;
  1255. background: white;
  1256. border-radius: 16rpx;
  1257. padding: 30rpx;
  1258. }
  1259. .upload-item {
  1260. margin-bottom: 40rpx;
  1261. &:last-child {
  1262. margin-bottom: 0;
  1263. }
  1264. }
  1265. .upload-label {
  1266. display: block;
  1267. font-size: 28rpx;
  1268. color: #333;
  1269. margin-bottom: 20rpx;
  1270. position: relative;
  1271. }
  1272. .required {
  1273. position: absolute;
  1274. left: -9px;
  1275. color: #f56c6c;
  1276. line-height: 20px;
  1277. font-size: 20px;
  1278. top: 3px;
  1279. }
  1280. .upload-btn {
  1281. display: flex;
  1282. flex-direction: column;
  1283. align-items: center;
  1284. justify-content: center;
  1285. width: 150rpx;
  1286. height: 150rpx;
  1287. border: 1rpx dashed #DCDFE6;
  1288. border-radius: 8rpx;
  1289. background: #fff;
  1290. }
  1291. .upload-tip {
  1292. font-size: 20rpx;
  1293. color: #666;
  1294. margin-top: 6rpx;
  1295. }
  1296. .format-tip {
  1297. display: block;
  1298. font-size: 24rpx;
  1299. color: #999;
  1300. margin-top: 15rpx;
  1301. }
  1302. .card-title {
  1303. font-size: 36rpx;
  1304. color: #333;
  1305. line-height: 60rpx;
  1306. margin-bottom: 20rpx;
  1307. }
  1308. ::v-deep .u-upload__wrap__preview__image{
  1309. width: 150rpx !important;
  1310. height: 150rpx !important;
  1311. }
  1312. :deep(.u-form-item) {
  1313. background-color: #fff;
  1314. border-radius: 16rpx;
  1315. padding: 8rpx 30rpx;
  1316. box-sizing: border-box;
  1317. margin-bottom: 20rpx;
  1318. }
  1319. :deep(.u-form-item__body__left__content__label) {
  1320. font-size: 28rpx !important;
  1321. color: #333 !important;
  1322. }
  1323. :deep(.u-radio-group--row) {
  1324. justify-content: flex-end;
  1325. }
  1326. .btn-view {
  1327. position: fixed;
  1328. bottom: 0;
  1329. left: 0;
  1330. right: 0;
  1331. background-color: #FFF;
  1332. padding: 20rpx 30rpx;
  1333. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
  1334. z-index: 5;
  1335. }
  1336. .submit {
  1337. height: 88rpx;
  1338. line-height: 88rpx;
  1339. background: #F8C008;
  1340. border-radius: 16rpx;
  1341. font-size: 32rpx;
  1342. font-weight: bold;
  1343. color: #333333;
  1344. border: none;
  1345. width: 100%;
  1346. &:active {
  1347. opacity: 0.8;
  1348. }
  1349. }
  1350. .unit {
  1351. color: #999;
  1352. font-size: 28rpx;
  1353. margin-left: 10rpx;
  1354. }
  1355. /* 编辑器样式 */
  1356. .editor-section {
  1357. background: white;
  1358. border-radius: 16rpx;
  1359. margin-bottom: 20rpx;
  1360. overflow: hidden;
  1361. }
  1362. .editor-header {
  1363. display: flex;
  1364. justify-content: space-between;
  1365. align-items: center;
  1366. padding: 30rpx;
  1367. border-bottom: 1rpx solid #f0f0f0;
  1368. }
  1369. .editor-label {
  1370. font-size: 28rpx;
  1371. color: #333;
  1372. font-weight: 500;
  1373. }
  1374. .word-count {
  1375. font-size: 24rpx;
  1376. color: #999;
  1377. }
  1378. :deep(.sp-editor) {
  1379. min-height: 400rpx;
  1380. }
  1381. /* 弹窗样式 */
  1382. .popup-content {
  1383. background: #fff;
  1384. border-radius: 20rpx 20rpx 0 0;
  1385. padding-bottom: env(safe-area-inset-bottom);
  1386. }
  1387. .popup-header {
  1388. padding: 30rpx;
  1389. text-align: center;
  1390. border-bottom: 1rpx solid #f0f0f0;
  1391. position: relative;
  1392. }
  1393. .popup-title {
  1394. font-size: 32rpx;
  1395. font-weight: 600;
  1396. color: #333;
  1397. }
  1398. .popup-actions {
  1399. display: flex;
  1400. padding: 30rpx;
  1401. gap: 20rpx;
  1402. }
  1403. .action-btn {
  1404. flex: 1;
  1405. height: 80rpx;
  1406. line-height: 80rpx;
  1407. border-radius: 12rpx;
  1408. font-size: 28rpx;
  1409. border: none;
  1410. &.cancel {
  1411. background: #f0f0f0;
  1412. color: #666;
  1413. }
  1414. &.confirm {
  1415. background: #F8C008;
  1416. color: #333;
  1417. }
  1418. }
  1419. :deep(.u-tabs__wrapper__nav__line) {
  1420. background-color: #F8C008 !important;
  1421. }
  1422. :deep(.u-form-item__body__right__message) {
  1423. text-align: right;
  1424. }
  1425. :deep(.u-textarea__field) {
  1426. text-align: right;
  1427. }
  1428. /* 规格样式 */
  1429. .pro-cate-container {
  1430. width: 100%;
  1431. padding: 16rpx;
  1432. background-color: #fff;
  1433. border-radius: 16rpx;
  1434. margin-bottom: 16rpx;
  1435. .cate-title {
  1436. font-size: 28rpx;
  1437. line-height: 44rpx;
  1438. color: #333;
  1439. margin-bottom: 16rpx;
  1440. }
  1441. .cate-add {
  1442. width: 100%;
  1443. height: 88rpx;
  1444. line-height: 88rpx;
  1445. text-align: center;
  1446. border-radius: 16rpx;
  1447. background-color: #FEF8E6;
  1448. color: #F8C008;
  1449. font-size: 32rpx;
  1450. font-weight: bold;
  1451. margin-top: 16rpx;
  1452. }
  1453. .cate-list {
  1454. .cate-item {
  1455. width: 100%;
  1456. background-color: #F9F7F0;
  1457. border-radius: 16rpx;
  1458. padding: 16rpx;
  1459. margin-bottom: 16rpx;
  1460. .upload-btn {
  1461. display: flex;
  1462. flex-direction: column;
  1463. align-items: center;
  1464. justify-content: center;
  1465. width: 100rpx;
  1466. height: 100rpx;
  1467. border: 1rpx dashed #DCDFE6;
  1468. border-radius: 8rpx;
  1469. background: #fff;
  1470. }
  1471. .cate-pic-line {
  1472. width: 100%;
  1473. height: 100rpx;
  1474. display: flex;
  1475. justify-content: space-between;
  1476. align-items: center;
  1477. margin-bottom: 16rpx;
  1478. ::v-deep .u-upload {
  1479. flex: 0;
  1480. margin-right: 16rpx;
  1481. width: 100rpx;
  1482. height: 100rpx;
  1483. }
  1484. ::v-deep .u-upload__wrap__preview {
  1485. margin: 0;
  1486. width: 100rpx;
  1487. height: 100rpx;
  1488. }
  1489. ::v-deep .u-upload__wrap__preview__image {
  1490. width: 100rpx !important;
  1491. height: 100rpx !important;
  1492. }
  1493. .cate-name {
  1494. width: 100rpx;
  1495. flex: 1;
  1496. color: #333;
  1497. font-size: 28rpx;
  1498. line-height: 44rpx;
  1499. }
  1500. }
  1501. .cate-ipt-line {
  1502. width: 100%;
  1503. display: flex;
  1504. align-items: center;
  1505. justify-content: space-between;
  1506. flex-wrap: nowrap;
  1507. padding-bottom: 16rpx;
  1508. ::v-deep .u-input {
  1509. height: 100rpx;
  1510. line-height: 100rpx;
  1511. }
  1512. .cate-ipt {
  1513. width: 48%;
  1514. flex: 1;
  1515. height: 100rpx;
  1516. background-color: #fff;
  1517. border-radius: 16rpx;
  1518. padding: 0 16rpx !important;
  1519. font-size: 28rpx;
  1520. .unit {
  1521. color: #333;
  1522. }
  1523. }
  1524. }
  1525. }
  1526. }
  1527. }
  1528. </style>