releaseProduct.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068
  1. <template>
  2. <view class="container">
  3. <view class="form-container">
  4. <up-form
  5. labelPosition="left"
  6. :model="formData"
  7. :rules="rules"
  8. ref="formRef"
  9. :labelWidth="100"
  10. >
  11. <!-- 商品分类 -->
  12. <view class="card-title">商品分类</view>
  13. <up-form-item
  14. class="form-item"
  15. label="商品分类"
  16. prop="categoryIds"
  17. :borderBottom="false"
  18. @click="showCategory = true"
  19. :required="true"
  20. >
  21. <up-input
  22. v-model="formData.categoryDisplayName"
  23. disabled
  24. disabledColor="#ffffff"
  25. placeholder="请选择商品分类"
  26. inputAlign="right"
  27. border="none"
  28. ></up-input>
  29. <template #right>
  30. <up-icon name="arrow-right"></up-icon>
  31. </template>
  32. </up-form-item>
  33. <!-- 商品信息 -->
  34. <view class="card-title">商品信息</view>
  35. <up-form-item
  36. label="商品名称"
  37. prop="storeName"
  38. :borderBottom="false"
  39. :required="true"
  40. >
  41. <up-input
  42. v-model="formData.storeName"
  43. placeholder="请输入商品名称"
  44. inputAlign="right"
  45. border="none"
  46. ></up-input>
  47. </up-form-item>
  48. <up-form-item
  49. label="商品关键字"
  50. prop="keyword"
  51. :borderBottom="false"
  52. :required="true"
  53. >
  54. <up-input
  55. v-model="formData.keyword"
  56. placeholder="请输入商品关键字"
  57. inputAlign="right"
  58. border="none"
  59. ></up-input>
  60. </up-form-item>
  61. <up-form-item
  62. label="商品简介"
  63. prop="storeInfo"
  64. :borderBottom="false"
  65. :required="true"
  66. >
  67. <up-textarea
  68. v-model="formData.storeInfo"
  69. placeholder="请输入商品简介"
  70. inputAlign="right"
  71. border="none"
  72. ></up-textarea>
  73. </up-form-item>
  74. <up-form-item
  75. label="单位"
  76. prop="unitName"
  77. :borderBottom="false"
  78. :required="true"
  79. >
  80. <up-input
  81. v-model="formData.unitName"
  82. placeholder="请输入单位"
  83. inputAlign="right"
  84. border="none"
  85. ></up-input>
  86. </up-form-item>
  87. <!-- 图片上传 -->
  88. <view class="upload-section">
  89. <view class="upload-item">
  90. <view class="upload-label"><text class="required">*</text>商品封面图</view>
  91. <up-upload
  92. :fileList="previewImages"
  93. @afterRead="async (e) => {
  94. await afterRead(e);
  95. getImage();
  96. }"
  97. @delete="onPreviewImageDelete"
  98. name="preview"
  99. max-count="1"
  100. :maxSize="5 * 1024 * 1024"
  101. accept=".png,.jpg,.jpeg"
  102. >
  103. <view class="upload-btn">
  104. <up-icon name="plus" size="20" color="#ccc"></up-icon>
  105. <text class="upload-tip">点击上传</text>
  106. </view>
  107. </up-upload>
  108. <text class="format-tip">支持上传PNG、JPG格式的图片,每张不超过5MB。</text>
  109. </view>
  110. </view>
  111. <view class="upload-section">
  112. <view class="upload-item">
  113. <view class="upload-label"><text class="required">*</text>商品图片</view>
  114. <up-upload
  115. :fileList="productImages"
  116. @afterRead="async (e) => {
  117. await afterRead(e);
  118. getImageProduct();
  119. }"
  120. @delete="onProductImageDelete"
  121. name="product"
  122. multiple
  123. :maxCount="10"
  124. :maxSize="5 * 1024 * 1024"
  125. accept=".png,.jpg,.jpeg"
  126. >
  127. <view class="upload-btn">
  128. <up-icon name="plus" size="20" color="#ccc"></up-icon>
  129. <text class="upload-tip">点击上传</text>
  130. </view>
  131. </up-upload>
  132. <text class="format-tip">支持上传PNG、JPG格式图片,每张不超过5MB,最多可上传5张。</text>
  133. </view>
  134. </view>
  135. <!-- 商品属性 -->
  136. <view class="card-title">商品属性</view>
  137. <up-form-item
  138. label="材质"
  139. prop="metalType"
  140. :borderBottom="false"
  141. :required="true"
  142. >
  143. <up-radio-group
  144. v-model="formData.metalType"
  145. placement="row"
  146. >
  147. <up-radio
  148. :customStyle="{marginRight: '20rpx'}"
  149. v-for="(item, index) in materialList"
  150. :key="index"
  151. :label="item.name"
  152. :name="item.code"
  153. >
  154. </up-radio>
  155. </up-radio-group>
  156. </up-form-item>
  157. <up-form-item
  158. label="重量"
  159. prop="weight"
  160. :borderBottom="false"
  161. :required="true"
  162. >
  163. <up-input
  164. v-model="formData.weight"
  165. placeholder="请输入重量"
  166. inputAlign="right"
  167. border="none"
  168. type="digit"
  169. ></up-input>
  170. <template #right>
  171. <text class="unit">g</text>
  172. </template>
  173. </up-form-item>
  174. <!-- 商品描述 -->
  175. <view class="card-title">商品描述</view>
  176. <view class="editor-section">
  177. <view class="editor-header">
  178. <text class="editor-label"><text class="required">*</text>商品描述</text>
  179. <text class="word-count">{{ descriptionText.length }}/500</text>
  180. </view>
  181. <sp-editor
  182. editorId="editor"
  183. :toolbar-config="toolbarConfig"
  184. :readOnly="readOnly"
  185. @input="onEditorInput"
  186. @upinImage="onUpinImage"
  187. @init="onEditorInit"
  188. @overMax="onOverMax"
  189. ></sp-editor>
  190. </view>
  191. <!-- 价格设置 -->
  192. <view class="card-title">价格设置</view>
  193. <up-form-item
  194. label="工费"
  195. prop="laborCost"
  196. :borderBottom="false"
  197. :required="true"
  198. >
  199. <up-input
  200. v-model="formData.laborCost"
  201. placeholder="请输入工费"
  202. inputAlign="right"
  203. border="none"
  204. type="digit"
  205. ></up-input>
  206. <template #right>
  207. <text class="unit">元/g</text>
  208. </template>
  209. </up-form-item>
  210. <up-form-item
  211. label="附加费"
  212. prop="additionalFee"
  213. :borderBottom="false"
  214. :required="true"
  215. >
  216. <up-input
  217. v-model="formData.additionalFee"
  218. placeholder="请输入附加费"
  219. inputAlign="right"
  220. border="none"
  221. type="digit"
  222. ></up-input>
  223. <template #right>
  224. <text class="unit">元</text>
  225. </template>
  226. </up-form-item>
  227. <up-form-item
  228. class="form-item"
  229. label="运费模板"
  230. prop="tempIds"
  231. :borderBottom="false"
  232. @click="showTemp = true"
  233. :required="true"
  234. >
  235. <up-input
  236. v-model="formData.tempName"
  237. disabled
  238. disabledColor="#ffffff"
  239. placeholder="请选择运费模板"
  240. inputAlign="right"
  241. border="none"
  242. ></up-input>
  243. <template #right>
  244. <up-icon name="arrow-right"></up-icon>
  245. </template>
  246. </up-form-item>
  247. <up-form-item
  248. label="库存"
  249. prop="stock"
  250. :borderBottom="false"
  251. :required="true"
  252. >
  253. <up-input
  254. v-model="formData.stock"
  255. placeholder="请输入库存"
  256. inputAlign="right"
  257. border="none"
  258. type="number"
  259. ></up-input>
  260. </up-form-item>
  261. <up-form-item
  262. label="商品编号"
  263. prop="barCode"
  264. :borderBottom="false"
  265. :required="true"
  266. >
  267. <up-input
  268. v-model="formData.barCode"
  269. placeholder="请输入商品编号"
  270. inputAlign="right"
  271. border="none"
  272. type="number"
  273. ></up-input>
  274. </up-form-item>
  275. <view class="upload-section">
  276. <view class="upload-item">
  277. <view class="upload-label"><text class="required">*</text>规格图片</view>
  278. <up-upload
  279. :fileList="productImagesGg"
  280. @afterRead="async (e) => {
  281. await afterRead(e);
  282. getImageProductGg();
  283. }"
  284. @delete="onProductImageGgDelete"
  285. name="productGg"
  286. :maxCount="1"
  287. :maxSize="5 * 1024 * 1024"
  288. accept=".png,.jpg,.jpeg"
  289. >
  290. <view class="upload-btn">
  291. <up-icon name="plus" size="20" color="#ccc"></up-icon>
  292. <text class="upload-tip">点击上传</text>
  293. </view>
  294. </up-upload>
  295. <text class="format-tip">支持上传PNG、JPG格式图片,每张不超过5MB,最多可上传5张。</text>
  296. </view>
  297. </view>
  298. <up-form-item
  299. label="商品排序"
  300. prop="sort"
  301. :borderBottom="false"
  302. >
  303. <up-input
  304. v-model="formData.sort"
  305. placeholder="请输入排序号"
  306. inputAlign="right"
  307. border="none"
  308. type="number"
  309. ></up-input>
  310. </up-form-item>
  311. </up-form>
  312. </view>
  313. <!-- 发布按钮 -->
  314. <view class="btn-view">
  315. <button class="submit" @click="submitForm">立即发布</button>
  316. </view>
  317. <!-- 类目选择弹窗 -->
  318. <up-popup
  319. :show="showCategory"
  320. @close="showCategory = false"
  321. mode="bottom"
  322. round="20"
  323. :closeable="true"
  324. >
  325. <view class="popup-content">
  326. <view class="popup-header">
  327. <text class="popup-title">选择商品分类</text>
  328. </view>
  329. <category-selector
  330. :categoryList="categoryData"
  331. :selectedIds="formData.categoryIds"
  332. @change="onCategoryChange"
  333. ref="categoryRef"
  334. />
  335. <view class="popup-actions">
  336. <button class="action-btn cancel" @click="showCategory = false">取消</button>
  337. <button class="action-btn confirm" @click="confirmCategory">确定</button>
  338. </view>
  339. </view>
  340. </up-popup>
  341. <!-- 运费模板-->
  342. <up-picker :show="showTemp" v-model="formData.tempIds" :columns="tempColumns" keyName="name" valueName ="id"
  343. confirmColor="#F8C008"
  344. @close="showTemp = false" @confirm="tempConfirm" @cancel="showTemp = false"></up-picker>
  345. </view>
  346. </template>
  347. <script setup>
  348. import { ref, computed, watch } from 'vue';
  349. import { onShow,onLoad } from "@dcloudio/uni-app";
  350. import { productCategory,productSave,templatesList,productInfo } from "@/api/merchant";
  351. import CategorySelector from '@/components/CategorySelector';
  352. import { useAppStore } from "@/stores/app";
  353. import { useImageUpload } from "@/hooks/useImageUpload";
  354. import { useToast } from "@/hooks/useToast";
  355. const { Toast } = useToast();
  356. const { imageList, afterRead, deletePic, uploadLoading } = useImageUpload({
  357. pid: 1,
  358. model: "product",
  359. });
  360. const appStore = useAppStore();
  361. const merchantInfo = appStore.userInfo.merchant
  362. // 表单数据
  363. const formData = ref({
  364. categoryIds: [], // 选中的分类ID数组
  365. categoryDisplayName: '', // 显示的分类名称
  366. tempName: '', // 显示的运费模板名称
  367. tempIds: [],
  368. storeName: '',
  369. keyword: '',
  370. storeInfo: '',
  371. unitName: '',
  372. metalType: '',
  373. weight: '',
  374. laborCost: '',
  375. additionalFee: '',
  376. sort: '',
  377. content: '',
  378. stock: '',
  379. barCode: ''
  380. });
  381. // 编辑器相关
  382. const editorIns = ref(null);
  383. const readOnly = ref(false);
  384. const descriptionText = ref('');
  385. const toolbarConfig = ref({
  386. iconSize: '20px',
  387. iconColumns: 10,
  388. excludeKeys: ['direction', 'date', 'lineHeight', 'letterSpacing', 'listCheck']
  389. });
  390. // 分类相关
  391. const showCategory = ref(false);
  392. const categoryData = ref([]);
  393. const categoryRef = ref();
  394. const showTemp = ref(false);
  395. const tempColumns = ref([])
  396. // 图片列表
  397. const previewImages = ref([]);
  398. const productImages = ref([]);
  399. const productImagesGg = ref([]);
  400. // 材质列表
  401. const materialList = ref([
  402. { name: '黄金',code:'au' },
  403. { name: '铂金' ,code:'pt'},
  404. { name: '白银',code:'ag' }
  405. ]);
  406. // 验证规则
  407. const rules = ref({
  408. categoryIds: {
  409. type: 'array',
  410. required: true,
  411. message: '请选择商品分类',
  412. trigger: ['change']
  413. },
  414. storeName: {
  415. type: 'string',
  416. required: true,
  417. message: '请输入商品名称',
  418. trigger: ['blur', 'change']
  419. },
  420. keyword: {
  421. type: 'string',
  422. required: true,
  423. message: '请输入商品关键字',
  424. trigger: ['blur', 'change']
  425. },
  426. storeInfo: {
  427. type: 'string',
  428. required: true,
  429. message: '请输入商品关键字',
  430. trigger: ['blur', 'change']
  431. },
  432. unitName: {
  433. type: 'string',
  434. required: true,
  435. message: '请输入单位',
  436. trigger: ['blur', 'change']
  437. },
  438. tempIds: {
  439. type: 'array',
  440. required: true,
  441. message: '请选择运费模板',
  442. trigger: ['blur', 'change']
  443. },
  444. metalType: {
  445. type: 'string',
  446. required: true,
  447. message: '请选择材质',
  448. trigger: ['change']
  449. },
  450. weight: {
  451. type: 'string',
  452. required: true,
  453. pattern: /^\d+(\.\d+)?$/,
  454. message: '重量必须是数字,可以是小数',
  455. trigger: ['blur', 'change']
  456. },
  457. laborCost: {
  458. type: 'string',
  459. required: true,
  460. pattern: /^\d+(\.\d+)?$/,
  461. message: '工费必须是数字,可以是小数',
  462. trigger: ['blur', 'change']
  463. },
  464. additionalFee: {
  465. type: 'string',
  466. required: true,
  467. pattern: /^\d+(\.\d+)?$/,
  468. message: '附加费必须是数字,可以是小数',
  469. trigger: ['blur', 'change']
  470. },
  471. sort: {
  472. type: 'string',
  473. pattern: /^\d*$/,
  474. message: '排序号必须是整数',
  475. trigger: ['blur', 'change']
  476. },
  477. stock: {
  478. type: 'string',
  479. required: true,
  480. pattern: /^\d*$/,
  481. message: '库存必须是整数',
  482. trigger: ['blur', 'change']
  483. },
  484. barCode: {
  485. type: 'string',
  486. required: true,
  487. pattern: /^\d*$/,
  488. message: '商品编号必须是整数',
  489. trigger: ['blur', 'change']
  490. },
  491. });
  492. // 页面加载
  493. onShow(() => {
  494. })
  495. onLoad(async (options)=>{
  496. await getProductCategory();
  497. await getTempData();
  498. console.log(options);
  499. if(options.id){
  500. await getProductDetail(options.id);
  501. }
  502. })
  503. // 获取商品分类
  504. async function getProductCategory(){
  505. let obj = {
  506. type: 1,
  507. status: -1
  508. }
  509. try {
  510. const { data } = await productCategory(obj)
  511. console.log('原始分类数据:', data);
  512. const newArr = []
  513. data.forEach((value, index) => {
  514. newArr[index] = value
  515. if (value.child) newArr[index].child = value.child.filter(item => item.status === true)
  516. })
  517. // 过滤商品分类设置为隐藏的子分类不出现在树形列表里
  518. categoryData.value = newArr.filter(item => item.code !== 'bb_mall')
  519. console.log('转换后的分类数据:', categoryData.value);
  520. } catch (error) {
  521. console.error('获取商品分类失败:', error);
  522. uni.showToast({ title: '获取分类失败', icon: 'none' });
  523. }
  524. }
  525. // 获取运费模板
  526. async function getTempData(){
  527. let obj = {
  528. page:1,
  529. limit:9999
  530. }
  531. try {
  532. const { data } = await templatesList()
  533. tempColumns.value[0] = data.list;
  534. } catch (error) {
  535. console.error('获取商品分类失败:', error);
  536. uni.showToast({ title: '获取分类失败', icon: 'none' });
  537. }
  538. }
  539. // 获取商品详情
  540. async function getProductDetail(id){
  541. try {
  542. const { data } = await productInfo(id)
  543. console.log('获取商品详情:',data);
  544. formData.value = data;
  545. productImages.value = [];
  546. formData.value.categoryIds =data.cateId.split(',') ;
  547. formData.value.categoryDisplayName = getCategoryDisplayName(formData.value.categoryIds);
  548. console.log(formData.value.categoryDisplayName)
  549. formData.value.tempName =formatterTemp(data.tempId);
  550. previewImages.value[0] = {};
  551. previewImages.value[0].url = data.image;
  552. const urlArr = JSON.parse(data.sliderImage);
  553. if(urlArr.length > 0){
  554. urlArr.forEach((item)=>{
  555. productImages.value.push({
  556. url:item
  557. })
  558. })
  559. }
  560. console.log(productImages.value)
  561. formData.value.additionalFee = data.attrValue[0].additionalAmount;
  562. formData.value.laborCost = data.attrValue[0].price;
  563. formData.value.barCode = data.attrValue[0].barCode;
  564. productImagesGg.value[0] = {};
  565. productImagesGg.value[0].url = data.attrValue[0].image;
  566. formData.value.stock = data.attrValue[0].stock;
  567. formData.value.weight = data.attrValue[0].weight;
  568. if (categoryRef.value && formData.value.categoryIds.length > 0) {
  569. // 给组件一点时间初始化
  570. setTimeout(() => {
  571. categoryRef.value.setSelectedIds(formData.value.categoryIds);
  572. }, 100);
  573. }
  574. } catch (error) {
  575. console.error('获取商品详情失败:', error);
  576. uni.showToast({ title: '获取商品详情失败', icon: 'none' });
  577. }
  578. }
  579. // 运费模板id获取中文名
  580. function formatterTemp(id) {
  581. // 假设 tempColumns 是一个数组
  582. const foundItem = tempColumns.value[0].find(item => item.id == id);
  583. return foundItem ? foundItem.name : '';
  584. }
  585. function tempConfirm(obj){
  586. formData.value.tempId = obj.value[0].id;
  587. formData.value.tempName = obj.value[0].name;
  588. showTemp.value = false;
  589. }
  590. // 分类选择变化
  591. const onCategoryChange = (result) => {
  592. console.log('分类选择变化:', result);
  593. // 这里只更新显示,不直接更新表单数据,等用户点击确定
  594. }
  595. // 确认分类选择
  596. const confirmCategory = () => {
  597. if (categoryRef.value) {
  598. const selectedIds = categoryRef.value.getSelectedIds()
  599. if (selectedIds.length === 0) {
  600. uni.showToast({ title: '请至少选择一个分类', icon: 'none' })
  601. return
  602. }
  603. // 更新表单数据
  604. formData.value.categoryIds = selectedIds
  605. formData.value.categoryDisplayName = getCategoryDisplayName(selectedIds)
  606. showCategory.value = false
  607. console.log('最终选中的分类ID:', selectedIds)
  608. }
  609. }
  610. // 根据选中的ID生成显示名称
  611. const getCategoryDisplayName = (selectedIds) => {
  612. if (!selectedIds || selectedIds.length === 0) return ''
  613. const names = []
  614. selectedIds.forEach(id => {
  615. // 检查是否是一级分类(表示全选)
  616. const firstLevel = categoryData.value.find(item => item.id == id)
  617. if (firstLevel) {
  618. // 如果是一级分类,显示"分类名称(全部)"
  619. names.push(`${firstLevel.name}`)
  620. } else {
  621. // 查找二级分类
  622. for (const parent of categoryData.value) {
  623. if (parent.child) {
  624. const secondLevel = parent.child.find(child => child.id == id)
  625. if (secondLevel) {
  626. names.push(`${parent.name}-${secondLevel.name}`)
  627. break
  628. }
  629. }
  630. }
  631. // 如果没有子分类的一级分类
  632. const singleLevel = categoryData.value.find(item =>
  633. !item.child && item.id == id
  634. )
  635. if (singleLevel) {
  636. names.push(singleLevel.name)
  637. }
  638. }
  639. })
  640. return names.join('、')
  641. }
  642. async function getImage() {
  643. console.log(imageList.value)
  644. if (imageList.value.length > 0) {
  645. if (imageList.value[0].status == "success") {
  646. previewImages.value = imageList.value;
  647. // change();
  648. } else {
  649. Toast({ title: "上传失败" });
  650. }
  651. }
  652. imageList.value = [];
  653. }
  654. async function getImageProduct() {
  655. console.log(imageList.value)
  656. if (imageList.value.length > 0) {
  657. if (imageList.value[0].status == "success") {
  658. productImages.value = [...productImages.value,...imageList.value];
  659. // change();
  660. } else {
  661. Toast({ title: "上传失败" });
  662. }
  663. }
  664. imageList.value = [];
  665. }
  666. async function getImageProductGg() {
  667. console.log(imageList.value)
  668. if (imageList.value.length > 0) {
  669. if (imageList.value[0].status == "success") {
  670. productImagesGg.value = imageList.value;
  671. // change();
  672. } else {
  673. Toast({ title: "上传失败" });
  674. }
  675. }
  676. imageList.value = [];
  677. }
  678. const onPreviewImageDelete =(e) => {
  679. previewImages.value.splice(e.index, 1);
  680. };
  681. const onProductImageDelete = (e) => {
  682. productImages.value.splice(e.index, 1);
  683. };
  684. const onProductImageGgDelete = (e) => {
  685. productImagesGg.value.splice(e.index, 1);
  686. };
  687. // 编辑器相关方法
  688. const onEditorInput = (e) => {
  689. descriptionText.value = e.text || '';
  690. formData.value.content = e.html || '';
  691. };
  692. const onEditorInit = (editor) => {
  693. editorIns.value = editor;
  694. };
  695. const onOverMax = (e) => {
  696. uni.showToast({ title: '内容长度超出限制', icon: 'none' });
  697. };
  698. const onUpinImage = (tempFiles, editorCtx) => {
  699. const filePath = tempFiles[0].tempFilePath || tempFiles[0].path;
  700. editorCtx.insertImage({
  701. src: filePath,
  702. width: '80%',
  703. success: () => {
  704. uni.showToast({ title: '图片插入成功', icon: 'success' });
  705. }
  706. });
  707. };
  708. // 表单验证和提交
  709. const formRef = ref(null);
  710. const validateForm = async () => {
  711. try {
  712. const valid = await formRef.value.validate();
  713. return valid;
  714. } catch (error) {
  715. console.error('表单验证失败:', error);
  716. return false;
  717. }
  718. };
  719. const submitForm = async () => {
  720. console.log(formData.value)
  721. // 检查商品描述
  722. if (!descriptionText.value.trim()) {
  723. uni.showToast({ title: '请输入商品描述', icon: 'none' });
  724. return;
  725. }
  726. // 检查图片上传
  727. if (previewImages.value.length === 0) {
  728. uni.showToast({ title: '请上传商品预览图', icon: 'none' });
  729. return;
  730. }
  731. if (productImages.value.length === 0) {
  732. uni.showToast({ title: '请上传商品图片', icon: 'none' });
  733. return;
  734. }
  735. if (productImagesGg.value.length === 0) {
  736. uni.showToast({ title: '请上传商品规格图片', icon: 'none' });
  737. return;
  738. }
  739. // 验证数字字段
  740. if (!/^\d+(\.\d+)?$/.test(formData.value.weight)) {
  741. uni.showToast({ title: '重量格式不正确', icon: 'none' });
  742. return;
  743. }
  744. if (!/^\d+(\.\d+)?$/.test(formData.value.laborCost)) {
  745. uni.showToast({ title: '工费格式不正确', icon: 'none' });
  746. return;
  747. }
  748. if (!/^\d+(\.\d+)?$/.test(formData.value.additionalFee)) {
  749. uni.showToast({ title: '附加费格式不正确', icon: 'none' });
  750. return;
  751. }
  752. if (!/^\d+$/.test(formData.value.stock)) {
  753. uni.showToast({ title: '库存必须是整数', icon: 'none' });
  754. return;
  755. }
  756. if (!/^\d+$/.test(formData.value.barCode)) {
  757. uni.showToast({ title: '商品编号必须是整数', icon: 'none' });
  758. return;
  759. }
  760. if (formData.value.sort && !/^\d+$/.test(formData.value.sort)) {
  761. uni.showToast({ title: '排序号必须是整数', icon: 'none' });
  762. return;
  763. }
  764. const valid = await validateForm();
  765. if (valid) {
  766. const submitData = {
  767. ...formData.value
  768. };
  769. submitData.cateId =formData.value.categoryIds.join(',');
  770. submitData.image =previewImages.value[0].url;
  771. const urlString = JSON.stringify(productImages.value.map(item => item.url));
  772. submitData.sliderImage =urlString;
  773. submitData.merchantId =parseInt(merchantInfo.id);
  774. submitData.specType = 0;
  775. submitData.isSub = false;
  776. submitData.attr =[
  777. {
  778. "attrName":"规格",
  779. "attrValues": "默认",
  780. "id": 0
  781. }
  782. ];
  783. submitData.attrValue =[
  784. {
  785. additionalAmount :formData.value.additionalFee,
  786. attrValue:"{\"规格\":\"默认\"}",
  787. barCode : formData.value.barCode,
  788. image : productImagesGg.value[0].url,
  789. price : formData.value.laborCost,
  790. stock : formData.value.stock,
  791. weight :formData.value.weight
  792. }
  793. ];
  794. console.log('提交数据:', submitData);
  795. const {data} = await productSave(submitData);
  796. uni.showToast({ title: '发布成功', icon: 'success' });
  797. uni.navigateTo({
  798. url: '/pages/merchantCenter/productCenter'
  799. })
  800. }
  801. };
  802. </script>
  803. <style scoped lang="scss">
  804. .container {
  805. background-color: #f8f8f8;
  806. min-height: 100vh;
  807. padding-bottom: 140rpx;
  808. }
  809. .form-container {
  810. padding: 30rpx;
  811. }
  812. .upload-section {
  813. margin-top: 20rpx;
  814. margin-bottom: 20rpx;
  815. background: white;
  816. border-radius: 16rpx;
  817. padding: 30rpx;
  818. }
  819. .upload-item {
  820. margin-bottom: 40rpx;
  821. &:last-child {
  822. margin-bottom: 0;
  823. }
  824. }
  825. .upload-label {
  826. display: block;
  827. font-size: 28rpx;
  828. color: #333;
  829. margin-bottom: 20rpx;
  830. position: relative;
  831. }
  832. .required {
  833. position: absolute;
  834. left: -9px;
  835. color: #f56c6c;
  836. line-height: 20px;
  837. font-size: 20px;
  838. top: 3px;
  839. }
  840. .upload-btn {
  841. display: flex;
  842. flex-direction: column;
  843. align-items: center;
  844. justify-content: center;
  845. width: 200rpx;
  846. height: 200rpx;
  847. border: 2rpx dashed #ccc;
  848. border-radius: 12rpx;
  849. background: #fafafa;
  850. }
  851. .upload-tip {
  852. font-size: 24rpx;
  853. color: #999;
  854. margin-top: 10rpx;
  855. }
  856. .format-tip {
  857. display: block;
  858. font-size: 24rpx;
  859. color: #999;
  860. margin-top: 15rpx;
  861. }
  862. .card-title {
  863. font-size: 36rpx;
  864. color: #333;
  865. line-height: 60rpx;
  866. margin-bottom: 20rpx;
  867. }
  868. :deep(.u-form-item) {
  869. background-color: #fff;
  870. border-radius: 16rpx;
  871. padding: 8rpx 30rpx;
  872. box-sizing: border-box;
  873. margin-bottom: 20rpx;
  874. }
  875. :deep(.u-form-item__body__left__content__label) {
  876. font-size: 28rpx !important;
  877. color: #333 !important;
  878. }
  879. :deep(.u-radio-group--row) {
  880. justify-content: flex-end;
  881. }
  882. .btn-view {
  883. position: fixed;
  884. bottom: 0;
  885. left: 0;
  886. right: 0;
  887. background-color: #FFF;
  888. padding: 20rpx 30rpx;
  889. box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.1);
  890. }
  891. .submit {
  892. height: 88rpx;
  893. line-height: 88rpx;
  894. background: #F8C008;
  895. border-radius: 16rpx;
  896. font-size: 32rpx;
  897. color: #333333;
  898. border: none;
  899. width: 100%;
  900. &:active {
  901. opacity: 0.8;
  902. }
  903. }
  904. .unit {
  905. color: #999;
  906. font-size: 28rpx;
  907. margin-left: 10rpx;
  908. }
  909. /* 编辑器样式 */
  910. .editor-section {
  911. background: white;
  912. border-radius: 16rpx;
  913. margin-bottom: 20rpx;
  914. overflow: hidden;
  915. }
  916. .editor-header {
  917. display: flex;
  918. justify-content: space-between;
  919. align-items: center;
  920. padding: 30rpx;
  921. border-bottom: 1rpx solid #f0f0f0;
  922. }
  923. .editor-label {
  924. font-size: 28rpx;
  925. color: #333;
  926. font-weight: 500;
  927. }
  928. .word-count {
  929. font-size: 24rpx;
  930. color: #999;
  931. }
  932. :deep(.sp-editor) {
  933. min-height: 400rpx;
  934. }
  935. /* 弹窗样式 */
  936. .popup-content {
  937. background: #fff;
  938. border-radius: 20rpx 20rpx 0 0;
  939. padding-bottom: env(safe-area-inset-bottom);
  940. }
  941. .popup-header {
  942. padding: 30rpx;
  943. text-align: center;
  944. border-bottom: 1rpx solid #f0f0f0;
  945. position: relative;
  946. }
  947. .popup-title {
  948. font-size: 32rpx;
  949. font-weight: 600;
  950. color: #333;
  951. }
  952. .popup-actions {
  953. display: flex;
  954. padding: 30rpx;
  955. gap: 20rpx;
  956. }
  957. .action-btn {
  958. flex: 1;
  959. height: 80rpx;
  960. line-height: 80rpx;
  961. border-radius: 12rpx;
  962. font-size: 28rpx;
  963. border: none;
  964. &.cancel {
  965. background: #f0f0f0;
  966. color: #666;
  967. }
  968. &.confirm {
  969. background: #F8C008;
  970. color: #333;
  971. }
  972. }
  973. :deep(.u-tabs__wrapper__nav__line){
  974. background-color: #F8C008 !important;
  975. }
  976. :deep(.u-form-item__body__right__message){
  977. text-align: right;
  978. }
  979. :deep(.u-textarea__field){
  980. text-align: right;
  981. }
  982. </style>