goods.js 64 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472
  1. define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
  2. var Controller = {
  3. index: () => {
  4. const { ref, reactive, onMounted, getCurrentInstance } = Vue
  5. const { ElMessageBox } = ElementPlus
  6. const index = {
  7. setup() {
  8. const { proxy } = getCurrentInstance();
  9. const state = reactive({
  10. data: [],
  11. order: 'desc',
  12. sort: 'id',
  13. filter: {
  14. drawer: false,
  15. data: {
  16. status: 'all',
  17. keyword: { field: 'id', value: '' },
  18. category_ids: 'all',
  19. activity_type: '',
  20. price: { min: '', max: '' },
  21. sales: { min: '', max: '' },
  22. },
  23. tools: {
  24. keyword: {
  25. type: 'tinputprepend',
  26. label: '商品信息',
  27. placeholder: '请输入查询内容',
  28. value: {
  29. field: 'id',
  30. value: '',
  31. },
  32. options: {
  33. data: [{
  34. label: '商品ID',
  35. value: 'id',
  36. },
  37. {
  38. label: '商品名称',
  39. value: 'title',
  40. },
  41. {
  42. label: '商品副标题',
  43. value: 'subtitle',
  44. }],
  45. }
  46. },
  47. // category_ids: {
  48. // type: 'tcascader',
  49. // label: '商品分类',
  50. // value: [],
  51. // options: {
  52. // data: [],
  53. // props: {
  54. // children: 'children',
  55. // label: 'name',
  56. // value: 'id',
  57. // checkStrictly: true,
  58. // emitPath: false,
  59. // multiple: true,
  60. // },
  61. // },
  62. // },
  63. activity_type: {
  64. type: 'tselect',
  65. label: '活动类型',
  66. value: '',
  67. options: {
  68. data: [],
  69. props: {
  70. label: 'name',
  71. value: 'type',
  72. },
  73. },
  74. },
  75. },
  76. condition: {},
  77. },
  78. statusData: {
  79. type: {
  80. up: 'success',
  81. down: 'danger',
  82. hidden: 'info',
  83. },
  84. color: {
  85. up: 'var(--el-color-success)',
  86. down: 'var(--el-color-danger)',
  87. hidden: 'var(--el-color-info)',
  88. }
  89. },
  90. })
  91. const type = reactive({
  92. data: []
  93. })
  94. function getTypeData() {
  95. Fast.api.ajax({
  96. url: 'shopro/goods/goods/getType',
  97. type: 'GET',
  98. }, function (ret, res) {
  99. type.data = res.data
  100. for (key in state.filter.tools) {
  101. if (key == 'activity_type') {
  102. state.filter.tools[key].options.data = res.data[key]
  103. }
  104. }
  105. return false
  106. }, function (ret, res) { })
  107. }
  108. const category = reactive({
  109. select: []
  110. })
  111. function getCategorySelect() {
  112. Fast.api.ajax({
  113. url: 'shopro/category/goodsSelect',
  114. type: 'GET',
  115. }, function (ret, res) {
  116. category.select = res.data
  117. // for (key in state.filter.tools) {
  118. // if (key == 'category_ids') {
  119. // state.filter.tools[key].options.data = res.data
  120. // }
  121. // }
  122. return false
  123. }, function (ret, res) { })
  124. }
  125. function getData() {
  126. let tempSearch = JSON.parse(JSON.stringify(state.filter.data));
  127. for (key in tempSearch) {
  128. if (key == 'price' || key == 'sales') {
  129. if (Number(tempSearch[key].min) && Number(tempSearch[key].max)) {
  130. tempSearch[key] = `${tempSearch[key].min} - ${tempSearch[key].max}`
  131. }
  132. }
  133. }
  134. let search = composeFilter(tempSearch, {
  135. title: 'like',
  136. subtitle: 'like',
  137. category_ids: {
  138. spacer: ',',
  139. },
  140. price: 'between',
  141. sales: 'between',
  142. });
  143. Fast.api.ajax({
  144. url: 'shopro/goods/goods',
  145. type: 'GET',
  146. data: {
  147. page: pagination.page,
  148. list_rows: pagination.list_rows,
  149. order: state.order,
  150. sort: state.sort,
  151. ...search,
  152. },
  153. }, function (ret, res) {
  154. state.data = res.data.data
  155. pagination.total = res.data.total
  156. return false
  157. }, function (ret, res) { })
  158. }
  159. function onChangeSort({ prop, order }) {
  160. state.order = order == 'ascending' ? 'asc' : 'desc';
  161. state.sort = prop;
  162. getData();
  163. }
  164. function onOpenFilter() {
  165. state.filter.drawer = true
  166. }
  167. function onChangeFilter() {
  168. pagination.page = 1
  169. getData()
  170. state.filter.drawer && (state.filter.drawer = false)
  171. }
  172. function onChangeTab() {
  173. pagination.page = 1
  174. getData()
  175. }
  176. const pagination = reactive({
  177. page: 1,
  178. list_rows: 10,
  179. total: 0,
  180. })
  181. const batchHandle = reactive({
  182. data: [],
  183. })
  184. function onChangeSelection(val) {
  185. batchHandle.data = val
  186. }
  187. function onBatchHandle(type) {
  188. let ids = []
  189. batchHandle.data.forEach((item) => {
  190. ids.push(item.id)
  191. })
  192. switch (type) {
  193. case 'delete':
  194. ElMessageBox.confirm('此操作将删除, 是否继续?', '提示', {
  195. confirmButtonText: '确定',
  196. cancelButtonText: '取消',
  197. type: 'warning',
  198. }).then(() => {
  199. onDelete(ids.join(','))
  200. });
  201. break;
  202. default:
  203. onCommand({ id: ids.join(','), type: type })
  204. break;
  205. }
  206. }
  207. function onCommand(item) {
  208. Fast.api.ajax({
  209. url: `shopro/goods/goods/edit/id/${item.id}`,
  210. type: 'POST',
  211. data: {
  212. status: item.type
  213. }
  214. }, function (ret, res) {
  215. getData()
  216. }, function (ret, res) { })
  217. }
  218. function onSkuCommand(item) {
  219. Fast.api.ajax({
  220. url: `shopro/goods/sku_price/edit/id/${item.id}`,
  221. type: 'POST',
  222. data: {
  223. status: item.type
  224. }
  225. }, function (ret, res) {
  226. getSkuPrice(item.goods_id);
  227. }, function (ret, res) { })
  228. }
  229. const expandRowKeys = reactive([]);
  230. function onExpand(id) {
  231. skuPrice.data = [];
  232. if (expandRowKeys.includes(id)) {
  233. expandRowKeys.length = 0;
  234. } else {
  235. expandRowKeys.length = 0;
  236. expandRowKeys.push(id);
  237. getSkuPrice(id);
  238. }
  239. }
  240. const skuPrice = reactive({
  241. data: []
  242. })
  243. function getSkuPrice(id) {
  244. Fast.api.ajax({
  245. url: `shopro/goods/sku_price?goods_id=${id}`,
  246. type: 'GET',
  247. }, function (ret, res) {
  248. skuPrice.data = res.data;
  249. return false
  250. }, function (ret, res) { })
  251. }
  252. function onOpenActivityDetail(activity) {
  253. Fast.api.open(`shopro/activity/activity/edit?type=edit&activity_type=${activity.type}&id=${activity.id}`, `${activity.type_text}活动`, {
  254. callback() {
  255. getData()
  256. }
  257. })
  258. }
  259. function onEditStock(item) {
  260. console.log('编辑库存')
  261. Fast.api.open(`shopro/goods/goods/addStock?id=${item.id}&stock=${item.stock || 0}&is_sku=${item.is_sku}`, "编辑库存", {
  262. callback() {
  263. getData()
  264. }
  265. })
  266. }
  267. function onAdd() {
  268. Fast.api.open("shopro/goods/goods/add?type=add", "添加", {
  269. callback() {
  270. getData()
  271. }
  272. })
  273. }
  274. function onEdit(id) {
  275. Fast.api.open(`shopro/goods/goods/edit?type=edit&id=${id}`, "编辑", {
  276. callback() {
  277. getData()
  278. }
  279. })
  280. }
  281. function onCopy(id) {
  282. Fast.api.open(`shopro/goods/goods/add?type=copy&id=${id}`, "复制", {
  283. callback() {
  284. getData()
  285. }
  286. })
  287. }
  288. function onDelete(id) {
  289. Fast.api.ajax({
  290. url: `shopro/goods/goods/delete/id/${id}`,
  291. type: 'DELETE',
  292. }, function (ret, res) {
  293. getData()
  294. }, function (ret, res) { })
  295. }
  296. function onRecyclebin() {
  297. Fast.api.open('shopro/goods/goods/recyclebin', "回收站", {
  298. callback() {
  299. getData()
  300. }
  301. })
  302. }
  303. function onChangeCategoryIds(val) {
  304. state.filter.data.category_ids = val?.id || 'all'
  305. pagination.page = 1
  306. getData();
  307. }
  308. const defaultExpandedKeys = ref([])
  309. function onFold() {
  310. for (var key in proxy.$refs.treeRef.store.nodesMap) {
  311. proxy.$refs.treeRef.store.nodesMap[key].expanded = false
  312. }
  313. defaultExpandedKeys.value = []
  314. }
  315. onMounted(() => {
  316. getTypeData()
  317. getCategorySelect()
  318. getData()
  319. })
  320. return {
  321. state,
  322. type,
  323. category,
  324. getData,
  325. onChangeSort,
  326. onOpenFilter,
  327. onChangeFilter,
  328. onChangeTab,
  329. pagination,
  330. batchHandle,
  331. onChangeSelection,
  332. onBatchHandle,
  333. onCommand,
  334. onSkuCommand,
  335. expandRowKeys,
  336. onExpand,
  337. skuPrice,
  338. getSkuPrice,
  339. onOpenActivityDetail,
  340. onEditStock,
  341. onAdd,
  342. onEdit,
  343. onCopy,
  344. onDelete,
  345. onRecyclebin,
  346. onChangeCategoryIds,
  347. onFold,
  348. defaultExpandedKeys,
  349. }
  350. }
  351. }
  352. createApp('index', index);
  353. },
  354. add: () => {
  355. Controller.form();
  356. },
  357. edit: () => {
  358. Controller.form();
  359. },
  360. form: () => {
  361. const { ref, reactive, onBeforeMount, onMounted, getCurrentInstance, watch, nextTick } = Vue
  362. const addEdit = {
  363. setup() {
  364. const { proxy } = getCurrentInstance();
  365. const state = reactive({
  366. type: new URLSearchParams(location.search).get('type'),
  367. id: new URLSearchParams(location.search).get('id'),
  368. activeStep: 0,
  369. tempData: {
  370. isStockWarning: false,
  371. }
  372. })
  373. const form = reactive({
  374. model: {
  375. type: 'normal', // 商品类型
  376. image: '',
  377. images: [],
  378. title: '',
  379. subtitle: '',
  380. category_ids: '',
  381. weigh: 0,
  382. sales_show_type: 'exact',
  383. show_sales: 0,
  384. limit_type: 'none',
  385. limit_num: 0,
  386. status: 'up',
  387. dispatch_type: 'express',
  388. dispatch_id: '',
  389. is_offline: 0,
  390. yushou: '',
  391. is_sku: 0,
  392. price: '',
  393. original_price: 0,
  394. cost_price: 0,
  395. stock_show_type: 'exact',
  396. stock: '',
  397. stock_warning: '',
  398. weight: '',
  399. sn: '',
  400. skus: [
  401. {
  402. id: 0,
  403. name: '',
  404. goods_id: 0,
  405. parent_id: 0,
  406. weigh: 0,
  407. children: [
  408. {
  409. id: 0,
  410. name: '',
  411. goods_id: 0,
  412. parent_id: 0,
  413. weigh: 0,
  414. },
  415. ],
  416. },
  417. ],
  418. sku_prices: [],
  419. service_ids: [],
  420. params: [],
  421. content: '',
  422. sku_prices: [],
  423. },
  424. rules: {
  425. image: [{ required: true, message: '请选择商品主图', trigger: 'blur' }],
  426. images: [{ required: true, message: '请选择轮播图', trigger: 'blur' }],
  427. title: [{ required: true, message: '请输入商品标题', trigger: 'blur' }],
  428. // category_ids: [{ required: true, message: '请输入商品分类', trigger: 'blur' }],
  429. dispatch_id: [{ required: true, message: '请选择物流快递', trigger: 'blur' }],
  430. price: [{ required: true, message: '请输入售卖价格', trigger: 'blur' }],
  431. },
  432. })
  433. function getDetail() {
  434. Fast.api.ajax({
  435. url: `shopro/goods/goods/detail/id/${state.id}`,
  436. type: 'GET',
  437. }, function (ret, res) {
  438. form.model = res.data;
  439. // 商品分类
  440. initCategoryIds()
  441. // 单规格
  442. if (form.model.is_sku == 0) {
  443. form.model.price = Number(form.model.price);
  444. state.tempData.isStockWarning = form.model.stock_warning ? true : false
  445. }
  446. // 多规格
  447. if (form.model.is_sku == 1) {
  448. getInit();
  449. }
  450. if (!form.model.skus) {
  451. form.model.skus = []
  452. form.model.sku_prices = []
  453. getInit();
  454. }
  455. form.model.params = form.model.params ? form.model.params : [];
  456. form.model.service_ids = form.model.service_ids ? form.model.service_ids : [];
  457. // 富文本
  458. Controller.api.bindevent();
  459. $('#goodsContent').html(form.model.content)
  460. getDispatchSelect()
  461. return false
  462. }, function (ret, res) { })
  463. }
  464. function onChangeGoodsType(type) {
  465. console.log(type, 'type')
  466. form.model.type = type;
  467. form.model.dispatch_type = type == 'normal' ? 'express' : 'autosend';
  468. form.model.dispatch_id = ''
  469. getDispatchSelect()
  470. }
  471. let categoryRef = {};
  472. const setCategoryRef = (el, tab) => {
  473. if (el) {
  474. categoryRef[tab.id + '-' + tab.name] = el;
  475. }
  476. };
  477. const tempCategory = reactive({
  478. tabActive: '',
  479. idsArr: {},
  480. label: {}
  481. })
  482. function initCategoryIds() {
  483. tempCategory.idsArr = {}
  484. form.model.category_ids_arr.forEach(item => {
  485. if (tempCategory.idsArr[item[0]]) {
  486. tempCategory.idsArr[item[0]].push(item.pop())
  487. } else {
  488. tempCategory.idsArr[item[0]] = []
  489. tempCategory.idsArr[item[0]].push(item.pop())
  490. }
  491. })
  492. onChangeCategoryIds()
  493. }
  494. function onChangeCategoryIds() {
  495. nextTick(() => {
  496. tempCategory.label = {}
  497. for (var key in categoryRef) {
  498. let keyArr = key.split('-');
  499. if (categoryRef[key].checkedNodes.length > 0) {
  500. categoryRef[key].checkedNodes.forEach((row) => {
  501. tempCategory.label[row.value] = keyArr[1] + '/' + row.pathLabels.join('/');
  502. });
  503. }
  504. }
  505. })
  506. }
  507. function onDeleteCategoryIds(id) {
  508. delete tempCategory.label[id];
  509. let idx = -1
  510. for (var key in tempCategory.idsArr) {
  511. tempCategory.idsArr[key].forEach((item, index) => {
  512. if (item == id) {
  513. idx = index
  514. }
  515. })
  516. if (idx != -1) {
  517. tempCategory.idsArr[key].splice(idx, 1)
  518. idx = -1
  519. }
  520. }
  521. }
  522. function onClearCategoryIds() {
  523. tempCategory.idsArr = {}
  524. tempCategory.label = {}
  525. }
  526. const category = reactive({
  527. select: []
  528. })
  529. function getCategorySelect(type) {
  530. Fast.api.ajax({
  531. url: 'shopro/category/select',
  532. type: 'GET',
  533. }, function (ret, res) {
  534. category.select = res.data
  535. // 分类选项卡赋值
  536. if (res.data.length > 0) {
  537. tempCategory.tabActive = res.data[0].id + ''
  538. }
  539. if (type) {
  540. if (state.type == 'edit' || state.type == 'copy') {
  541. getDetail()
  542. } else {
  543. getInit();
  544. getDispatchSelect()
  545. nextTick(() => {
  546. Controller.api.bindevent();
  547. })
  548. }
  549. }
  550. return false
  551. }, function (ret, res) { })
  552. }
  553. function onAddCategory() {
  554. Fast.api.open("shopro/category/add?type=add", "添加", {
  555. callback() {
  556. getCategorySelect()
  557. }
  558. })
  559. }
  560. const dispatch = reactive({
  561. select: []
  562. })
  563. function getDispatchSelect() {
  564. Fast.api.ajax({
  565. url: 'shopro/dispatch/dispatch/select',
  566. type: 'GET',
  567. data: {
  568. type: form.model.dispatch_type
  569. }
  570. }, function (ret, res) {
  571. dispatch.select = res.data
  572. return false
  573. }, function (ret, res) { })
  574. }
  575. function onAddDispatch(dispatch_type) {
  576. Fast.api.open(`shopro/dispatch/dispatch/add?type=add&dispatch_type=${dispatch_type}`, "添加", {
  577. callback() {
  578. getDispatchSelect()
  579. }
  580. })
  581. }
  582. function onChangeDispatchType(val) {
  583. form.model.dispatch_id = val == 'custom' ? 0 : ''
  584. getDispatchSelect()
  585. }
  586. const service = reactive({
  587. select: []
  588. })
  589. function getServiceSelect() {
  590. Fast.api.ajax({
  591. url: 'shopro/goods/service/select',
  592. type: 'GET',
  593. }, function (ret, res) {
  594. service.select = res.data
  595. return false
  596. }, function (ret, res) { })
  597. }
  598. function onAddService() {
  599. Fast.api.open("shopro/goods/service/add?type=add", "添加", {
  600. callback() {
  601. getServiceSelect()
  602. }
  603. })
  604. }
  605. function onBack() {
  606. state.activeStep--;
  607. }
  608. function onNext() {
  609. // proxy.$refs['formRef'].validate((valid) => {
  610. // if (valid) {
  611. state.activeStep++;
  612. // } else {
  613. // return false;
  614. // }
  615. // });
  616. }
  617. const validateData = ref({
  618. 0: 0,
  619. 1: 0,
  620. 2: 0,
  621. 3: 0,
  622. 4: 0,
  623. })
  624. function isValidate() {
  625. nextTick(async () => {
  626. for (var key in validateData.value) {
  627. await proxy.$refs[`formRef${key}`].validate((valid) => {
  628. if (valid) {
  629. validateData.value[key] = 0;
  630. } else {
  631. validateData.value[key] = 1;
  632. }
  633. });
  634. }
  635. })
  636. }
  637. const isEditInit = ref(false);
  638. function getInit() {
  639. let tempIdArr = {};
  640. for (let i in form.model.skus) {
  641. // 为每个 规格增加当前页面自增计数器,比较唯一用
  642. form.model.skus[i]['temp_id'] = countId.value++;
  643. for (let j in form.model.skus[i]['children']) {
  644. // 为每个 规格项增加当前页面自增计数器,比较唯一用
  645. form.model.skus[i]['children'][j]['temp_id'] = countId.value++;
  646. // 记录规格项真实 id 对应的 临时 id
  647. tempIdArr[form.model.skus[i]['children'][j]['id']] =
  648. form.model.skus[i]['children'][j]['temp_id'];
  649. }
  650. }
  651. for (var i = 0; i < form.model.sku_prices.length; i++) {
  652. let tempSkuPrice = form.model.sku_prices[i];
  653. tempSkuPrice['temp_id'] = i + 1;
  654. // 将真实 id 数组,循环,找到对应的临时 id 组合成数组
  655. tempSkuPrice['goods_sku_temp_ids'] = [];
  656. let goods_sku_id_arr = tempSkuPrice['goods_sku_ids'].split(',');
  657. for (let ids of goods_sku_id_arr) {
  658. tempSkuPrice['goods_sku_temp_ids'].push(tempIdArr[ids]);
  659. }
  660. form.model.sku_prices[i] = tempSkuPrice;
  661. }
  662. if (state.type == 'copy') {
  663. for (let i in form.model.skus) {
  664. // 为每个 规格增加当前页面自增计数器,比较唯一用
  665. form.model.skus[i].id = 0;
  666. for (let j in form.model.skus[i]['children']) {
  667. form.model.skus[i]['children'][j].id = 0;
  668. }
  669. }
  670. }
  671. if (form.model.sku_prices.length > 0) {
  672. form.model.sku_prices.forEach((si) => {
  673. si.stock_warning_switch = false;
  674. if (si.stock_warning || si.stock_warning == 0) {
  675. si.stock_warning_switch = true;
  676. }
  677. });
  678. }
  679. setTimeout(() => {
  680. isEditInit.value = true;
  681. }, 200);
  682. }
  683. //添加主规格
  684. const skuModal = ref('');
  685. const countId = ref(1);
  686. function addMainSku() {
  687. form.model.skus.push({
  688. id: 0,
  689. temp_id: countId.value++,
  690. name: skuModal.value,
  691. pid: 0,
  692. children: [],
  693. });
  694. skuModal.value = '';
  695. buildSkuPriceTable();
  696. }
  697. function deleteMainSku(k) {
  698. let data = form.model.skus[k];
  699. // 删除主规格
  700. form.model.skus.splice(k, 1);
  701. // 如果当前删除的主规格存在子规格,则清空 skuPrice, 不存在子规格则不清空
  702. if (data.children.length > 0) {
  703. form.model.sku_prices = []; // 规格大变化,清空skuPrice
  704. isResetSku.value = 1; // 重置规格
  705. }
  706. buildSkuPriceTable();
  707. }
  708. //添加子规格
  709. const isResetSku = ref(0);
  710. const childrenModal = [];
  711. function addChildrenSku(k) {
  712. let isExist = false;
  713. form.model.skus[k].children.forEach((e) => {
  714. if (e.name == childrenModal[k] && e.name != '') {
  715. isExist = true;
  716. }
  717. });
  718. if (isExist) {
  719. alert('子规格已存在');
  720. return false;
  721. }
  722. form.model.skus[k].children.push({
  723. id: 0,
  724. temp_id: countId.value++,
  725. name: childrenModal[k],
  726. pid: form.model.skus[k].id,
  727. });
  728. childrenModal[k] = '';
  729. // 如果是添加的第一个子规格,清空 skuPrice
  730. if (form.model.skus[k].children.length == 1) {
  731. form.model.sku_prices = []; // 规格大变化,清空skuPrice
  732. isResetSku.value = 1; // 重置规格
  733. }
  734. buildSkuPriceTable();
  735. }
  736. function deleteChildrenSku(k, i) {
  737. let data = form.model.skus[k].children[i];
  738. form.model.skus[k].children.splice(i, 1);
  739. // 查询 skuPrice 中包含被删除的的子规格的项,然后移除
  740. let deleteArr = [];
  741. form.model.sku_prices.forEach((item, index) => {
  742. item.goods_sku_text.forEach((e, i) => {
  743. if (e == data.name) {
  744. deleteArr.push(index);
  745. }
  746. });
  747. });
  748. deleteArr.sort(function (a, b) {
  749. return b - a;
  750. });
  751. // 移除有相关子规格的项
  752. deleteArr.forEach((i, e) => {
  753. form.model.sku_prices.splice(i, 1);
  754. });
  755. // 当前规格项,所有子规格都被删除,清空 skuPrice
  756. if (form.model.skus[k].children.length <= 0) {
  757. form.model.sku_prices = []; // 规格大变化,清空skuPrice
  758. isResetSku.value = 1; // 重置规格
  759. }
  760. buildSkuPriceTable();
  761. }
  762. watch(
  763. () => form.model.skus,
  764. () => {
  765. if (isEditInit.value && form.model.is_sku) {
  766. buildSkuPriceTable();
  767. }
  768. },
  769. { deep: true },
  770. );
  771. //组成新的规格
  772. function buildSkuPriceTable() {
  773. let arr = [];
  774. //遍历sku子规格生成新数组,然后执行递归笛卡尔积
  775. form.model.skus.forEach((s1, k1) => {
  776. let children = s1.children;
  777. let childrenIdArray = [];
  778. if (children.length > 0) {
  779. children.forEach((s2, k2) => {
  780. childrenIdArray.push(s2.temp_id);
  781. });
  782. // 如果 children 子规格数量为 0,则不渲染当前规格, (相当于没有这个主规格)
  783. arr.push(childrenIdArray);
  784. }
  785. });
  786. recursionSku(arr, 0, []);
  787. }
  788. //递归找笛卡尔规格集合
  789. function recursionSku(arr, k, temp) {
  790. if (k == arr.length && k != 0) {
  791. let tempDetail = [];
  792. let tempDetailIds = [];
  793. temp.forEach((item, index) => {
  794. for (let sku of form.model.skus) {
  795. for (let child of sku.children) {
  796. if (item == child.temp_id) {
  797. tempDetail.push(child.name);
  798. tempDetailIds.push(child.temp_id);
  799. }
  800. }
  801. }
  802. });
  803. let flag = false; // 默认添加新的
  804. for (let i = 0; i < form.model.sku_prices.length; i++) {
  805. if (form.model.sku_prices[i].goods_sku_temp_ids.join(',') == tempDetailIds.join(',')) {
  806. flag = i;
  807. break;
  808. }
  809. }
  810. if (flag === false) {
  811. form.model.sku_prices.push({
  812. id: 0,
  813. temp_id: form.model.sku_prices.length + 1,
  814. goods_sku_ids: '',
  815. goods_id: 0,
  816. weigh: 0,
  817. image: '',
  818. stock: 0,
  819. stock_warning: null,
  820. stock_warning_switch: false,
  821. price: 0,
  822. sn: '',
  823. weight: 0,
  824. status: 'up',
  825. goods_sku_text: tempDetail,
  826. goods_sku_temp_ids: tempDetailIds,
  827. });
  828. } else {
  829. form.model.sku_prices[flag].goods_sku_text = tempDetail;
  830. form.model.sku_prices[flag].goods_sku_temp_ids = tempDetailIds;
  831. }
  832. return;
  833. }
  834. if (arr.length) {
  835. for (let i = 0; i < arr[k].length; i++) {
  836. temp[k] = arr[k][i];
  837. recursionSku(arr, k + 1, temp);
  838. }
  839. }
  840. }
  841. const batchPopover = reactive({
  842. flag: {
  843. price: false,
  844. original_price: false,
  845. cost_price: false,
  846. weight: false,
  847. sn: false,
  848. },
  849. value: ''
  850. })
  851. function onbatchPopover(type, oper) {
  852. switch (oper) {
  853. case 'confirm':
  854. form.model.sku_prices.forEach((i) => {
  855. i[type] = batchPopover.value;
  856. });
  857. batchPopover.value = '';
  858. batchPopover.flag[type] = false;
  859. break;
  860. case 'cancel':
  861. batchPopover.value = '';
  862. batchPopover.flag[type] = false;
  863. break;
  864. }
  865. }
  866. function onChangeStockWarningSwitch(index) {
  867. form.model.sku_prices[index].stock_warning = form.model.sku_prices[index].stock_warning_switch
  868. ? 0
  869. : null;
  870. }
  871. const paramsRules = {
  872. title: [{ required: true, message: '请输入名称', trigger: 'blur' }],
  873. content: [{ required: true, message: '请输入内容', trigger: 'blur' }],
  874. };
  875. function onAddParams() {
  876. form.model.params.push({
  877. title: '',
  878. content: '',
  879. });
  880. }
  881. function onDeleteParams(index) {
  882. form.model.params.splice(index, 1);
  883. }
  884. function onSuccess(data) {
  885. form.model.image_wh = {
  886. w: data.image_width,
  887. h: data.image_height,
  888. };
  889. }
  890. function onConfirm() {
  891. isValidate()
  892. setTimeout(() => {
  893. if (validateData.value[0] == 0 && validateData.value[1] == 0 && validateData.value[2] == 0) {
  894. let submitForm = JSON.parse(JSON.stringify(form.model));
  895. let idsArr = [];
  896. for (var key in tempCategory.idsArr) {
  897. idsArr.push(...tempCategory.idsArr[key]);
  898. }
  899. submitForm.category_ids = idsArr.join(',');
  900. if (submitForm.is_sku == 0) {
  901. if (!state.tempData.isStockWarning) {
  902. submitForm.stock_warning = null
  903. }
  904. }
  905. if (submitForm.is_sku == 1) {
  906. delete submitForm.price;
  907. delete submitForm.original_price;
  908. delete submitForm.cost_price;
  909. delete submitForm.stock_show_type
  910. delete submitForm.stock;
  911. delete submitForm.stock_warning;
  912. delete submitForm.weight;
  913. delete submitForm.sn;
  914. }
  915. submitForm.content = $("#goodsContent").val()
  916. if (state.type == 'copy') {
  917. delete submitForm.id;
  918. }
  919. // 虚拟商品is_offline=0
  920. if (submitForm.type == 'virtual') {
  921. submitForm.is_offline = 0
  922. }
  923. Fast.api.ajax({
  924. url: state.type == 'add' || state.type == 'copy' ? 'shopro/goods/goods/add' : `shopro/goods/goods/edit/id/${state.id}`,
  925. type: 'POST',
  926. data: submitForm
  927. }, function (ret, res) {
  928. Fast.api.close()
  929. }, function (ret, res) { })
  930. }
  931. }, 500)
  932. }
  933. onBeforeMount(() => {
  934. getCategorySelect(true)
  935. getServiceSelect()
  936. })
  937. return {
  938. state,
  939. form,
  940. onChangeGoodsType,
  941. categoryRef,
  942. setCategoryRef,
  943. tempCategory,
  944. onChangeCategoryIds,
  945. onDeleteCategoryIds,
  946. onClearCategoryIds,
  947. category,
  948. onAddCategory,
  949. dispatch,
  950. getDispatchSelect,
  951. onAddDispatch,
  952. onChangeDispatchType,
  953. service,
  954. onAddService,
  955. onBack,
  956. onNext,
  957. validateData,
  958. isValidate,
  959. isEditInit,
  960. getInit,
  961. skuModal,
  962. countId,
  963. addMainSku,
  964. deleteMainSku,
  965. isResetSku,
  966. childrenModal,
  967. addChildrenSku,
  968. deleteChildrenSku,
  969. buildSkuPriceTable,
  970. recursionSku,
  971. batchPopover,
  972. onbatchPopover,
  973. onChangeStockWarningSwitch,
  974. paramsRules,
  975. onAddParams,
  976. onDeleteParams,
  977. onSuccess,
  978. onConfirm
  979. }
  980. }
  981. }
  982. createApp('addEdit', addEdit);
  983. },
  984. select: () => {
  985. const { reactive, onMounted, watch, getCurrentInstance, nextTick } = Vue
  986. const select = {
  987. setup() {
  988. const { proxy } = getCurrentInstance();
  989. const state = reactive({
  990. data_type: new URLSearchParams(location.search).get('data_type'),
  991. multiple: new URLSearchParams(location.search).get('multiple') || false,
  992. max: new URLSearchParams(location.search).get('max') || 0,
  993. ids: new URLSearchParams(location.search).get('ids'), // 选中的商品ids
  994. goods_ids: new URLSearchParams(location.search).get('goods_ids'), // 需要搜索的商品id列表
  995. data: [],
  996. filter: {
  997. drawer: false,
  998. data: {
  999. category_ids: '',
  1000. keyword: '',
  1001. price: {
  1002. min: '',
  1003. max: '',
  1004. },
  1005. },
  1006. tools: {},
  1007. },
  1008. })
  1009. state.ids = state.ids ? state.ids.split(',') : []
  1010. const category = reactive({
  1011. id: '',
  1012. select: [],
  1013. detail: []
  1014. })
  1015. function getCategorySelect() {
  1016. Fast.api.ajax({
  1017. url: 'shopro/category/select',
  1018. type: 'GET',
  1019. }, function (ret, res) {
  1020. category.select = res.data
  1021. category.select.unshift({
  1022. children: [],
  1023. id: 'all',
  1024. name: '全部分类',
  1025. });
  1026. if (category.select.length > 0) {
  1027. category.id = category.select[0].id;
  1028. getCategoryDetail();
  1029. }
  1030. return false
  1031. }, function (ret, res) { })
  1032. }
  1033. function getCategoryDetail() {
  1034. const data = category.select.find((item) => item.id == category.id);
  1035. category.detail = data.children || [];
  1036. state.filter.data.category_ids = data.id;
  1037. }
  1038. function changeCategoryIds(data) {
  1039. state.filter.data.category_ids = data.id;
  1040. }
  1041. function getData() {
  1042. let tempSearch = JSON.parse(JSON.stringify(state.filter.data));
  1043. if (tempSearch.price.min && tempSearch.price.max) {
  1044. tempSearch.price = `${tempSearch.price.min} - ${tempSearch.price.max}`;
  1045. }
  1046. // activity_type 搜索
  1047. if (state.activity_type) {
  1048. tempSearch.activity_type = state.activity_type;
  1049. }
  1050. // id 搜索
  1051. if (state.goods_ids) {
  1052. tempSearch.goods = { field: 'id', value: state.goods_ids };
  1053. }
  1054. let search = composeFilter(tempSearch, {
  1055. keyword: 'like',
  1056. price: 'between',
  1057. });
  1058. Fast.api.ajax({
  1059. url: 'shopro/goods/goods/select',
  1060. type: 'GET',
  1061. data: {
  1062. data_type: state.data_type,
  1063. type: 'page',
  1064. page: pagination.page,
  1065. list_rows: pagination.list_rows,
  1066. ...search,
  1067. },
  1068. }, function (ret, res) {
  1069. state.data = res.data.data
  1070. pagination.total = res.data.total
  1071. nextTick(() => {
  1072. state.data.forEach((l) => {
  1073. if (state.ids?.includes(l.id + '')) {
  1074. proxy.$refs['multipleTableRef']?.toggleRowSelection(l, true);
  1075. toggleRowSelection('row', [l], l);
  1076. }
  1077. });
  1078. });
  1079. return false
  1080. }, function (ret, res) { })
  1081. }
  1082. watch(() => state.filter.data, () => {
  1083. pagination.page = 1;
  1084. getData()
  1085. }, {
  1086. deep: true,
  1087. })
  1088. const pagination = reactive({
  1089. page: 1,
  1090. list_rows: 10,
  1091. total: 0,
  1092. })
  1093. function onSelect(selection, row) {
  1094. if (
  1095. !state.max ||
  1096. (state.max && state.max > state.ids.length)
  1097. ) {
  1098. if (state.ids.includes(row.id + '')) {
  1099. let index = state.ids.findIndex((id) => id == row.id);
  1100. state.ids.splice(index, 1);
  1101. } else {
  1102. state.ids.push(row.id);
  1103. }
  1104. }
  1105. toggleRowSelection('row', selection, row);
  1106. }
  1107. function onSelectAll(selection) {
  1108. if (
  1109. !state.max ||
  1110. (state.max && state.max > state.ids.length + selection.length)
  1111. ) {
  1112. if (selection.length == 0) {
  1113. state.data.forEach((l) => {
  1114. if (state.ids.includes(l.id)) {
  1115. let index = state.ids.findIndex((id) => id == l.id);
  1116. state.ids.splice(index, 1);
  1117. }
  1118. });
  1119. } else {
  1120. state.data.forEach((l) => {
  1121. if (!state.ids.includes(l.id)) {
  1122. state.ids.push(l.id);
  1123. }
  1124. });
  1125. }
  1126. }
  1127. toggleRowSelection('all', selection);
  1128. }
  1129. function toggleRowSelection(type, selection, row) {
  1130. // 限制数量
  1131. if (state.max && state.max < selection.length) {
  1132. if (type == 'row') {
  1133. proxy.$refs['multipleTableRef'].toggleRowSelection(row, false);
  1134. } else if (type == 'all') {
  1135. proxy.$refs['multipleTableRef']?.clearSelection();
  1136. state.data.forEach((l) => {
  1137. if (state.ids?.includes(l.id)) {
  1138. proxy.$refs['multipleTableRef']?.toggleRowSelection(l, true);
  1139. }
  1140. });
  1141. }
  1142. ElMessage({
  1143. type: 'warning',
  1144. message: '已到选择上限',
  1145. });
  1146. return false;
  1147. }
  1148. }
  1149. function onSingleSelect(item) {
  1150. Fast.api.close(item)
  1151. }
  1152. function onConfirm() {
  1153. let ids = state.ids.join(',')
  1154. Fast.api.ajax({
  1155. url: 'shopro/goods/goods/select',
  1156. type: 'GET',
  1157. data: {
  1158. type: 'select',
  1159. search: JSON.stringify({ id: [ids, 'in'] })
  1160. },
  1161. }, function (ret, res) {
  1162. Fast.api.close(res.data)
  1163. return false
  1164. }, function (ret, res) { })
  1165. }
  1166. onMounted(() => {
  1167. getCategorySelect()
  1168. getData()
  1169. })
  1170. return {
  1171. state,
  1172. category,
  1173. getCategoryDetail,
  1174. getData,
  1175. changeCategoryIds,
  1176. pagination,
  1177. onSelect,
  1178. onSelectAll,
  1179. onSingleSelect,
  1180. onConfirm
  1181. }
  1182. }
  1183. }
  1184. createApp('select', select);
  1185. },
  1186. recyclebin: () => {
  1187. const { reactive, onMounted } = Vue
  1188. const { ElMessageBox } = ElementPlus
  1189. const recyclebin = {
  1190. setup() {
  1191. const state = reactive({
  1192. data: [],
  1193. order: '',
  1194. sort: '',
  1195. })
  1196. function getData() {
  1197. Fast.api.ajax({
  1198. url: 'shopro/goods/goods/recyclebin',
  1199. type: 'GET',
  1200. data: {
  1201. page: pagination.page,
  1202. list_rows: pagination.list_rows,
  1203. order: state.order,
  1204. sort: state.sort,
  1205. },
  1206. }, function (ret, res) {
  1207. state.data = res.data.data
  1208. pagination.total = res.data.total
  1209. return false
  1210. }, function (ret, res) { })
  1211. }
  1212. function onChangeSort({ prop, order }) {
  1213. state.order = order == 'ascending' ? 'asc' : 'desc';
  1214. state.sort = prop;
  1215. getData();
  1216. }
  1217. const pagination = reactive({
  1218. page: 1,
  1219. list_rows: 10,
  1220. total: 0,
  1221. })
  1222. const batchHandle = reactive({
  1223. data: [],
  1224. })
  1225. function onChangeSelection(val) {
  1226. batchHandle.data = val
  1227. }
  1228. function onBatchHandle(type) {
  1229. let ids = []
  1230. batchHandle.data.forEach((item) => {
  1231. ids.push(item.id)
  1232. })
  1233. switch (type) {
  1234. case 'restore':
  1235. onRestore(ids.join(','))
  1236. break;
  1237. case 'destroy':
  1238. ElMessageBox.confirm('此操作将销毁, 是否继续?', '提示', {
  1239. confirmButtonText: '确定',
  1240. cancelButtonText: '取消',
  1241. type: 'warning',
  1242. }).then(() => {
  1243. onDestroy(ids.join(','))
  1244. });
  1245. break;
  1246. case 'all':
  1247. ElMessageBox.confirm('此操作将清空回收站, 是否继续?', '提示', {
  1248. confirmButtonText: '确定',
  1249. cancelButtonText: '取消',
  1250. type: 'warning',
  1251. }).then(() => {
  1252. onDestroy('all')
  1253. });
  1254. break;
  1255. }
  1256. }
  1257. function onRestore(id) {
  1258. Fast.api.ajax({
  1259. url: `shopro/goods/goods/restore/id/${id}`,
  1260. type: 'POST',
  1261. }, function (ret, res) {
  1262. getData()
  1263. }, function (ret, res) { })
  1264. }
  1265. function onDestroy(id) {
  1266. Fast.api.ajax({
  1267. url: `shopro/goods/goods/destroy/id/${id}`,
  1268. type: 'POST',
  1269. }, function (ret, res) {
  1270. getData()
  1271. }, function (ret, res) { })
  1272. }
  1273. onMounted(() => {
  1274. getData()
  1275. })
  1276. return {
  1277. state,
  1278. getData,
  1279. onChangeSort,
  1280. pagination,
  1281. batchHandle,
  1282. onChangeSelection,
  1283. onBatchHandle,
  1284. onRestore,
  1285. onDestroy,
  1286. }
  1287. }
  1288. }
  1289. createApp('recyclebin', recyclebin);
  1290. },
  1291. addstock: () => {
  1292. const { reactive, onMounted, getCurrentInstance, watch } = Vue
  1293. const addStock = {
  1294. setup() {
  1295. const { proxy } = getCurrentInstance();
  1296. const state = reactive({
  1297. id: new URLSearchParams(location.search).get('id'),
  1298. is_sku: new URLSearchParams(location.search).get('is_sku'),
  1299. stock: new URLSearchParams(location.search).get('stock') || 0,
  1300. })
  1301. const form = reactive({
  1302. model: {
  1303. add_stock: '',
  1304. sku_prices: [
  1305. {
  1306. id: '',
  1307. goods_sku_text: [],
  1308. add_stock: '',
  1309. },
  1310. ],
  1311. },
  1312. rules: {
  1313. add_stock: [
  1314. { required: true, message: '请输入补充库存', trigger: 'change' },
  1315. {
  1316. pattern: /^(-)?[1-9][0-9]*$/,
  1317. message: '只能输入正整数、负整数',
  1318. trigger: 'change',
  1319. },
  1320. ],
  1321. },
  1322. })
  1323. const batchPopover = reactive({
  1324. flag: false,
  1325. add_stock: '',
  1326. })
  1327. function onBatchPopover(type) {
  1328. if (type == 'cancel') {
  1329. batchPopover.add_stock = '';
  1330. batchPopover.flag = false;
  1331. } else {
  1332. form.model.sku_prices.forEach((i) => {
  1333. i.add_stock = batchPopover.add_stock;
  1334. });
  1335. batchPopover.add_stock = '';
  1336. batchPopover.flag = false;
  1337. }
  1338. }
  1339. function getSkuPrice() {
  1340. Fast.api.ajax({
  1341. url: `shopro/goods/sku_price?goods_id=${state.id}`,
  1342. type: 'GET',
  1343. }, function (ret, res) {
  1344. form.model.sku_prices = res.data;
  1345. return false
  1346. }, function (ret, res) { })
  1347. }
  1348. function onConfirm() {
  1349. proxy.$refs['formRef'].validate((valid) => {
  1350. if (valid) {
  1351. Fast.api.ajax({
  1352. url: `shopro/goods/goods/addStock/id/${state.id}`,
  1353. type: 'POST',
  1354. data: JSON.parse(JSON.stringify(form.model))
  1355. }, function (ret, res) {
  1356. Fast.api.close()
  1357. }, function (ret, res) { })
  1358. }
  1359. });
  1360. }
  1361. onMounted(() => {
  1362. getSkuPrice()
  1363. })
  1364. return {
  1365. state,
  1366. form,
  1367. batchPopover,
  1368. onBatchPopover,
  1369. getSkuPrice,
  1370. onConfirm
  1371. }
  1372. }
  1373. }
  1374. createApp('addStock', addStock);
  1375. },
  1376. api: {
  1377. bindevent: function () {
  1378. Form.api.bindevent($("form[role=form]"));
  1379. },
  1380. },
  1381. };
  1382. return Controller;
  1383. });