sku.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. <template>
  2. <el-drawer :title="titleMap[mode]" :append-to-body="true" v-model="visible" :size="800" destroy-on-close :close-on-click-modal="false" @closed="$emit('closed')" :with-header="false">
  3. <el-container class="flex-column" v-loading="loading">
  4. <div class="drawer-detail-main">
  5. <div class="drawer-detail-header">
  6. <div class="drawer-detail-header-body">
  7. <div class="drawer-detail-header-left">{{ titleMap[mode] }}</div>
  8. <div class="drawer-detail-header-left">
  9. <el-button type="default" icon="el-icon-close" @click="visible=false"></el-button>
  10. </div>
  11. </div>
  12. </div>
  13. </div>
  14. <el-main>
  15. <el-form ref="dialogForm" :model="formData" :rules="rules" label-width="100px" label-position="top">
  16. <fieldset>
  17. <legend><el-tag>基础信息</el-tag></legend>
  18. <el-form-item label="SKU名称" prop="name">
  19. <el-input v-model="formData.name" clearable maxlength="40" show-word-limit placeholder="请输入"></el-input>
  20. <div class="el-form-item-msg"></div>
  21. </el-form-item>
  22. <el-form-item label="商品原价" prop="line_price">
  23. <el-input v-model="formData.line_price" clearable placeholder="请输入" type="number">
  24. <template #append>元</template>
  25. </el-input>
  26. <div class="el-form-item-msg"></div>
  27. </el-form-item>
  28. <el-form-item label="顾客支付价" prop="price">
  29. <el-input v-model="formData.price" readonly clearable placeholder="请输入" type="number">
  30. <template #append>元</template>
  31. </el-input>
  32. <div class="el-form-item-msg"></div>
  33. </el-form-item>
  34. <el-form-item label="商品编号" prop="sku_id">
  35. <el-input v-model="formData.sku_id" clearable placeholder="请输入">
  36. <template #append>
  37. <el-button @click="createSkuId">重新获取</el-button>
  38. </template>
  39. </el-input>
  40. <div class="el-form-item-msg"></div>
  41. </el-form-item>
  42. <el-form-item label="规格图片" prop="image">
  43. <sc-upload v-model="formData.image" title="规格图片" :cropper="true" :compress="1" :aspectRatio="1/1"></sc-upload>
  44. <div class="el-form-item-msg"></div>
  45. </el-form-item>
  46. </fieldset>
  47. <fieldset>
  48. <legend><el-tag>商品搭配</el-tag></legend>
  49. <el-button type="primary" size="small" @click="addSpecs" v-if="specs.length<5">添加分组</el-button>
  50. <div class="specs" v-for="(item,indx) in specs" :key="indx">
  51. <div class="specs-header">
  52. <div class="specs-left">
  53. <el-input v-model="item.name" placeholder="请输入商品组名称" maxlength="10" show-word-limit clearable style="width: 50%;"></el-input>
  54. </div>
  55. <div class="specs-right">
  56. <el-select v-model="item.num" placeholder="请选择">
  57. <el-option :value="0" label="全部可选"></el-option>
  58. <el-option :value="len" :label="item.list.length+'选'+len" v-for="len in item.list.length" v-if="item.list.length > 1"></el-option>
  59. </el-select>
  60. </div>
  61. </div>
  62. <div class="specs-tips">商品组名称中不可出现“任选N”“X选N”字样,当商品组的可选范围是“几选1”或“全部可选”时,不可配置重复选</div>
  63. <div class="specs-body">
  64. <div class="body-item" v-for="(its,ind) in item.list" :key="ind">
  65. <div class="item-left">
  66. <el-row :gutter="10">
  67. <el-col :span="12" class="mb10">
  68. <el-input placeholder="请输入单品名称 " v-model="its.name">
  69. <template #prepend>名称</template>
  70. </el-input>
  71. </el-col>
  72. <el-col :span="12" class="mb10">
  73. <el-input placeholder="单价" v-model="its.price" type="number">
  74. <template #prepend>单价</template>
  75. <template #append>元</template>
  76. </el-input>
  77. </el-col>
  78. <el-col :span="12" class="mb10">
  79. <el-input placeholder="请输入重量 " v-model="its.weight" type="number">
  80. <template #prepend>重量</template>
  81. <template #append>
  82. <el-select v-model="its.weight_unit">
  83. <el-option value="kg" label="kg(千克)"></el-option>
  84. <el-option value="g" label="g(克)"></el-option>
  85. <el-option value="L" label="L(升)"></el-option>
  86. <el-option value="ml" label="ml(毫升)"></el-option>
  87. <el-option value="m" label="m(米)"></el-option>
  88. <el-option value="m²" label="m²(平方米)"></el-option>
  89. <el-option value="m³" label="m³(立方米)"></el-option>
  90. </el-select>
  91. </template>
  92. </el-input>
  93. </el-col>
  94. <el-col :span="12" class="mb10">
  95. <el-input placeholder="数量" v-model="its.number" type="number">
  96. <template #prepend>数量</template>
  97. <template #append>份</template>
  98. </el-input>
  99. </el-col>
  100. </el-row>
  101. </div>
  102. <div class="item-dels">
  103. <el-button type="danger" @click="removeItem(indx,ind)" circle icon="el-icon-delete" size="small"></el-button>
  104. </div>
  105. </div>
  106. </div>
  107. <div class="specs-footer">
  108. <el-button type="primary" @click="addItem(indx)" size="small">增加单品</el-button>
  109. <el-button type="danger" @click="removeSpec(indx)" size="small">删除组</el-button>
  110. </div>
  111. </div>
  112. </fieldset>
  113. </el-form>
  114. </el-main>
  115. <el-footer style="text-align: right;">
  116. <el-button @click="visible=false" >取 消</el-button>
  117. <el-button v-if="mode!='show'" type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
  118. </el-footer>
  119. </el-container>
  120. </el-drawer>
  121. </template>
  122. <script>
  123. export default {
  124. data(){
  125. return {
  126. loading: false,
  127. mode:"add",
  128. titleMap:{
  129. add:"新增商品",
  130. edit:"SKU详情"
  131. },
  132. visible: false,
  133. isSaveing: false,
  134. specs:[{"name":"","num":0,"list":[ {"name":"","price":1,"weight":"1","weight_unit":'kg',"number":1} ]}],
  135. formData:{},
  136. skuIndex:0,
  137. rules:{
  138. sku_id: [
  139. {required: true, message: '请输入'}
  140. ],
  141. name: [
  142. {required: true, message: '请输入'}
  143. ],
  144. line_price: [
  145. {required: true, message: '请输入'}
  146. ],
  147. price: [
  148. {required: true, message: '请输入'}
  149. ],
  150. }
  151. }
  152. },
  153. methods:{
  154. createSkuId(){
  155. this.formData.sku_id = this.generate()
  156. },
  157. generate() {
  158. // 1. 获取当前毫秒时间戳(13位)
  159. let timestamp = Date.now();
  160. // 避免同一毫秒内重复(若时间戳相同,sequence递增)
  161. if (timestamp === this.lastTimestamp) {
  162. this.sequence++;
  163. } else {
  164. this.sequence = 0;
  165. this.lastTimestamp = timestamp;
  166. }
  167. // 2. 随机数部分:生成 4 位随机数(保证总长度灵活调整)
  168. const randomPart = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
  169. // 3. 组合:时间戳(13位) + 序列号(最多2位,可调) + 随机数(4位) → 总长度可变
  170. // 目标18位,所以需要控制各部分长度
  171. // 这里设计:时间戳后6位 + 序列号(2位) + 随机数(4位) + 补足剩余长度
  172. const timestampPart = timestamp.toString().slice(-6); // 取时间戳后6位
  173. const seqPart = this.sequence.toString().padStart(2, '0'); // 序列号占2位(0-99)
  174. // 组合得到12位,还需要6位随机数(总18位)
  175. let base = timestampPart + seqPart + randomPart; // 6+2+4=12位
  176. const remainingLength = 18 - base.length;
  177. const extraRandom = Math.floor(Math.random() * Math.pow(10, remainingLength)).toString().padStart(remainingLength, '0');
  178. return base + extraRandom; // 最终18位字符串
  179. },
  180. open(mode = 'add'){
  181. this.mode = mode;
  182. this.visible = true;
  183. return this
  184. },
  185. //表单注入数据
  186. setData(index,data){
  187. this.formData = JSON.parse(JSON.stringify(data));
  188. this.specs = this.formData.specs;
  189. this.skuIndex = index;
  190. },
  191. addSpecs(){
  192. if (this.specs.length >= 5) {
  193. return this.$message.error("最多只能添加三组规格")
  194. }
  195. this.specs.push({"name":"","num":0,"list":[ {"name":"","price":1,"weight":"1","weight_unit":'kg',"number":1} ]});
  196. },
  197. addItem(index){
  198. if (this.specs[index].list.length >= 20) {
  199. return this.$message.error("最多只能添加20个")
  200. }
  201. this.specs[index].list.push({"name":"","price":1,"weight":"1","weight_unit":'kg',"number":1})
  202. },
  203. removeSpec(index){
  204. this.specs.splice(index,1)
  205. },
  206. removeItem(index,sub){
  207. this.specs[index].list.splice(sub,1)
  208. },
  209. async submit(){
  210. var validate = await this.$refs.dialogForm.validate().catch(()=>{});
  211. if(!validate){ return false }
  212. this.visible = false;
  213. this.$emit("success",{"index":this.skuIndex,"data":this.formData});
  214. this.formData = {};
  215. }
  216. }
  217. }
  218. </script>
  219. <style>
  220. .specs{margin-top: 15px;background-color: #f8f8f8;}
  221. .specs-header{
  222. display: flex;
  223. background-color: #ddd;
  224. padding: 10px;
  225. }
  226. .specs-tips{
  227. padding: 0 10px 10px 10px;
  228. background-color: #ddd;
  229. }
  230. .specs-header .specs-left{
  231. display: flex;
  232. align-items: center;
  233. flex: 1;
  234. gap: 10px;
  235. }
  236. .specs-header .specs-right{
  237. display: flex;
  238. margin-left: auto;
  239. margin-right: 0;
  240. }
  241. .specs-body{display: flex;align-items: center;gap: 10px;padding: 10px;flex-direction: row;flex-wrap: wrap;}
  242. .specs-body .body-item{padding: 5px;display: flex;align-items: center;}
  243. .specs-body .body-item .item-dels{margin-left: 10px;}
  244. .mb10{margin-bottom: 10px;}
  245. .specs-footer{background-color: #ddd;padding: 10px;}
  246. .sku-table{margin-top: 10px;}
  247. </style>