commit
						9cac5a2937
					
				|  | @ -91,6 +91,7 @@ async function handleBatchDelete() { | |||
|   }); | ||||
|   try { | ||||
|     await deletePurchaseOrderList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success({ | ||||
|       content: $t('ui.actionMessage.deleteSuccess'), | ||||
|       key: 'action_process_msg', | ||||
|  |  | |||
|  | @ -102,6 +102,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteCodegenTableList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -84,6 +84,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteConfigList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -66,6 +66,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo01ContactList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -76,6 +76,7 @@ async function onDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03StudentList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -75,6 +75,7 @@ async function onDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03CourseList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -75,6 +75,7 @@ async function onDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03GradeList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -75,6 +75,7 @@ async function onDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03StudentList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -78,6 +78,7 @@ async function onDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03StudentList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -123,6 +123,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo01ContactList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -134,6 +134,7 @@ async function onDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03StudentList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -81,6 +81,7 @@ async function onDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03CourseList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -81,6 +81,7 @@ async function onDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03GradeList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -130,6 +130,7 @@ async function onDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03StudentList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -124,6 +124,7 @@ async function onDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03StudentList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -84,6 +84,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteFileList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -118,6 +118,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteFileConfigList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -130,6 +130,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteJobList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -93,6 +93,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDeptList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -90,6 +90,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDictDataList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -88,6 +88,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDictTypeList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -76,6 +76,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteMailAccountList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -88,6 +88,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteMailTemplateList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -77,6 +77,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteNoticeList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -94,6 +94,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteNotifyTemplateList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -83,6 +83,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deletePostList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -96,6 +96,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteRoleList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -87,6 +87,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteSmsChannelList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success({ | ||||
|       content: $t('ui.actionMessage.deleteSuccess', ['短信渠道']), | ||||
|       key: 'action_key_msg', | ||||
|  |  | |||
|  | @ -94,6 +94,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteSmsTemplateList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -95,6 +95,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteTenantList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -76,6 +76,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteTenantPackageList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -118,6 +118,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteUserList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     message.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -90,6 +90,7 @@ async function onDelete(row: InfraCodegenApi.CodegenTable) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该代码生成配置吗?'); | ||||
|   await deleteCodegenTableList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -69,6 +69,7 @@ async function onDelete(row: InfraConfigApi.Config) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该参数吗?'); | ||||
|   await deleteConfigList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -57,6 +57,7 @@ async function onDelete(row: InfraDataSourceConfigApi.DataSourceConfig) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该数据源吗?'); | ||||
|   await deleteDataSourceConfigList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -64,6 +64,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo01ContactList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -70,6 +70,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03StudentList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -69,6 +69,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03CourseList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -69,6 +69,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03GradeList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -69,6 +69,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03StudentList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -64,6 +64,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03StudentList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -121,6 +121,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo01ContactList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -133,6 +133,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03StudentList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -77,6 +77,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03CourseList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -77,6 +77,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03GradeList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -129,6 +129,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03StudentList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -122,6 +122,7 @@ async function handleDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteDemo03StudentList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|  |  | |||
|  | @ -79,6 +79,7 @@ async function onDelete(row: InfraFileApi.File) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该文件吗?'); | ||||
|   await deleteFileList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -103,6 +103,7 @@ async function onDelete(row: InfraFileConfigApi.FileConfig) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该文件配置吗?'); | ||||
|   await deleteFileConfigList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -122,6 +122,7 @@ async function onDelete(row: InfraJobApi.Job) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该任务吗?'); | ||||
|   await deleteJobList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import type { MallSpuApi } from '#/api/mall/product/spu'; | ||||
| import type { PropertyAndValues } from './model'; | ||||
| 
 | ||||
| import type { MallSpuApi } from '#/api/mall/product/spu'; | ||||
| 
 | ||||
| /** | ||||
|  * 获得商品的规格列表 - 商品相关的公共函数 | ||||
|  * | ||||
|  |  | |||
|  | @ -1,14 +1,16 @@ | |||
| <script lang="ts" setup> | ||||
| import { useVbenForm } from '#/adapter/form'; | ||||
| import * as ExpressTemplateApi from '#/api/mall/trade/delivery/expressTemplate'; | ||||
| import { watch } from 'vue'; | ||||
| 
 | ||||
| import { ElMessage } from 'element-plus'; | ||||
| import { DICT_TYPE, getIntDictOptions, DeliveryTypeEnum } from '#/utils'; | ||||
| 
 | ||||
| import { useVbenForm } from '#/adapter/form'; | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
|   propFormData: Object; | ||||
| }>(); | ||||
| 
 | ||||
| const emit = defineEmits(['update:activeName']); | ||||
| 
 | ||||
| /** 将传进来的值赋值给 formData */ | ||||
| watch( | ||||
|   () => props.propFormData, | ||||
|  | @ -20,7 +22,6 @@ watch( | |||
|   }, | ||||
| ); | ||||
| 
 | ||||
| const emit = defineEmits(['update:activeName']); | ||||
| const validate = async () => { | ||||
|   const { valid } = await formApi.validate(); | ||||
|   if (!valid) { | ||||
|  | @ -29,10 +30,10 @@ const validate = async () => { | |||
|   try { | ||||
|     // 校验通过更新数据 | ||||
|     Object.assign(props.propFormData, formApi.getValues()); | ||||
|   } catch (e) { | ||||
|   } catch (error) { | ||||
|     ElMessage.error('【商品详情】不完善,请填写相关信息'); | ||||
|     emit('update:activeName', 'description'); | ||||
|     throw e; // 目的截断之后的校验 | ||||
|     throw error; // 目的截断之后的校验 | ||||
|   } | ||||
| }; | ||||
| defineExpose({ validate }); | ||||
|  |  | |||
|  | @ -1,5 +1,3 @@ | |||
| import SkuList from './SkuList.vue'; | ||||
| 
 | ||||
| interface PropertyAndValues { | ||||
|   id: number; | ||||
|   name: string; | ||||
|  | @ -22,4 +20,6 @@ interface RuleConfig { | |||
|   message: string; | ||||
| } | ||||
| 
 | ||||
| export { SkuList, PropertyAndValues, RuleConfig, getPropertyList }; | ||||
| export { getPropertyList, PropertyAndValues, RuleConfig }; | ||||
| 
 | ||||
| export { default as SkuList } from './SkuList.vue'; | ||||
|  |  | |||
|  | @ -1,14 +1,16 @@ | |||
| <script lang="ts" setup> | ||||
| import { useVbenForm } from '#/adapter/form'; | ||||
| import * as ExpressTemplateApi from '#/api/mall/trade/delivery/expressTemplate'; | ||||
| import { watch } from 'vue'; | ||||
| 
 | ||||
| import { ElMessage } from 'element-plus'; | ||||
| import { DICT_TYPE, getIntDictOptions, DeliveryTypeEnum } from '#/utils'; | ||||
| 
 | ||||
| import { useVbenForm } from '#/adapter/form'; | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
|   propFormData: Object; | ||||
| }>(); | ||||
| 
 | ||||
| const emit = defineEmits(['update:activeName']); | ||||
| 
 | ||||
| /** 将传进来的值赋值给 formData */ | ||||
| watch( | ||||
|   () => props.propFormData, | ||||
|  | @ -20,7 +22,6 @@ watch( | |||
|   }, | ||||
| ); | ||||
| 
 | ||||
| const emit = defineEmits(['update:activeName']); | ||||
| const validate = async () => { | ||||
|   const { valid } = await formApi.validate(); | ||||
|   if (!valid) { | ||||
|  | @ -29,10 +30,10 @@ const validate = async () => { | |||
|   try { | ||||
|     // 校验通过更新数据 | ||||
|     Object.assign(props.propFormData, formApi.getValues()); | ||||
|   } catch (e) { | ||||
|   } catch (error) { | ||||
|     ElMessage.error('【其它设置】不完善,请填写相关信息'); | ||||
|     emit('update:activeName', 'other'); | ||||
|     throw e; // 目的截断之后的校验 | ||||
|     throw error; // 目的截断之后的校验 | ||||
|   } | ||||
| }; | ||||
| defineExpose({ validate }); | ||||
|  |  | |||
|  | @ -1,4 +1,149 @@ | |||
| <!-- 商品发布 - 库存价格 - 属性列表 --> | ||||
| <script lang="ts" setup> | ||||
| import type { PropType } from 'vue'; | ||||
| 
 | ||||
| import type { PropertyAndValues } from './model'; | ||||
| 
 | ||||
| import type { MallPropertyApi } from '#/api/mall/product/property'; | ||||
| 
 | ||||
| import { computed, ref, watch } from 'vue'; | ||||
| 
 | ||||
| import { | ||||
|   ElButton, | ||||
|   ElCol, | ||||
|   ElDivider, | ||||
|   ElMessage, | ||||
|   ElSpace, | ||||
|   ElTag, | ||||
|   ElText, | ||||
| } from 'element-plus'; | ||||
| 
 | ||||
| import * as PropertyApi from '#/api/mall/product/property'; | ||||
| import { $t } from '#/locales'; | ||||
| 
 | ||||
| defineOptions({ name: 'ProductAttributes' }); | ||||
| 
 | ||||
| // 商品属性名称下拉框 | ||||
| const props = defineProps({ | ||||
|   propertyList: { | ||||
|     type: Array as PropType<PropertyAndValues[]>, | ||||
|     default: () => [], | ||||
|   }, | ||||
| }); /** 输入框失去焦点或点击回车时触发 */ | ||||
| const emit = defineEmits(['success']); | ||||
| const inputValue = ref(''); // 输入框值 | ||||
| const attributeIndex = ref<null | number>(null); // 获取焦点时记录当前属性项的index | ||||
| // 输入框显隐控制 | ||||
| const inputVisible = computed(() => (index: number) => { | ||||
|   if (attributeIndex.value === null) return false; | ||||
|   if (attributeIndex.value === index) return true; | ||||
| }); | ||||
| const inputRef = ref<any[]>([]); // 标签输入框Ref | ||||
| /** 解决 ref 在 v-for 中的获取问题*/ | ||||
| const setInputRef = (el: any) => { | ||||
|   if (el === null || el === undefined) return; | ||||
|   // 如果不存在 id 相同的元素才添加 | ||||
|   if ( | ||||
|     !inputRef.value.some( | ||||
|       (item) => item.inputRef?.attributes.id === el.inputRef?.attributes.id, | ||||
|     ) | ||||
|   ) { | ||||
|     inputRef.value.push(el); | ||||
|   } | ||||
| }; | ||||
| const attributeList = ref<PropertyAndValues[]>([]); // 商品属性列表 | ||||
| const attributeOptions = ref([] as MallPropertyApi.PropertyValue[]); | ||||
| watch( | ||||
|   () => props.propertyList, | ||||
|   (data) => { | ||||
|     if (!data) return; | ||||
|     attributeList.value = data as any; | ||||
|   }, | ||||
|   { | ||||
|     deep: true, | ||||
|     immediate: true, | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| /** 删除属性值*/ | ||||
| const handleCloseValue = (index: number, valueIndex: number) => { | ||||
|   if (index < attributeList.value.length) { | ||||
|     const values = attributeList.value[index]!.values as any[]; | ||||
|     values.splice(valueIndex, 1); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 删除属性*/ | ||||
| const handleCloseProperty = (index: number) => { | ||||
|   if (index < attributeList.value.length) { | ||||
|     attributeList.value.splice(index, 1); | ||||
|     emit('success', attributeList.value); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 显示输入框并获取焦点 */ | ||||
| const showInput = async (index: number) => { | ||||
|   if (index < attributeList.value.length) { | ||||
|     attributeIndex.value = index; | ||||
|     inputRef.value[index].focus(); | ||||
|     // 获取属性下拉选项 | ||||
|     await getAttributeOptions(attributeList.value[index]!.id); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // 定义 success 事件,用于操作成功后的回调 | ||||
| const handleInputConfirm = async (index: number, propertyId: number) => { | ||||
|   if (inputValue.value && index < attributeList.value.length) { | ||||
|     // 1. 重复添加校验 | ||||
|     const values = attributeList.value[index]!.values as any[]; | ||||
|     if (values.find((item) => item.name === inputValue.value)) { | ||||
|       ElMessage.warning('已存在相同属性值,请重试'); | ||||
|       attributeIndex.value = null; | ||||
|       inputValue.value = ''; | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 2.1 情况一:属性值已存在,则直接使用并结束 | ||||
|     const existValue = attributeOptions.value.find( | ||||
|       (item) => item.name === inputValue.value, | ||||
|     ); | ||||
|     if (existValue) { | ||||
|       attributeIndex.value = null; | ||||
|       inputValue.value = ''; | ||||
|       const values = attributeList.value[index]!.values as any[]; | ||||
|       values.push({ | ||||
|         id: existValue.id!, | ||||
|         name: existValue.name, | ||||
|       }); | ||||
|       emit('success', attributeList.value); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 2.2 情况二:新属性值,则进行保存 | ||||
|     try { | ||||
|       const id = await PropertyApi.createPropertyValue({ | ||||
|         propertyId, | ||||
|         name: inputValue.value, | ||||
|       }); | ||||
|       const values = attributeList.value[index]!.values as any[]; | ||||
|       values.push({ id, name: inputValue.value }); | ||||
|       ElMessage.success($t('common.createSuccess')); | ||||
|       emit('success', attributeList.value); | ||||
|     } catch { | ||||
|       ElMessage.error('添加失败,请重试'); | ||||
|     } | ||||
|   } | ||||
|   attributeIndex.value = null; | ||||
|   inputValue.value = ''; | ||||
| }; | ||||
| 
 | ||||
| /** 获取商品属性下拉选项 */ | ||||
| const getAttributeOptions = async (propertyId: number) => { | ||||
|   attributeOptions.value = | ||||
|     await PropertyApi.getPropertyValueSimpleList(propertyId); | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <ElCol v-for="(item, index) in attributeList" :key="index"> | ||||
|     <div> | ||||
|  | @ -53,144 +198,3 @@ | |||
|     <ElDivider class="my-10px" /> | ||||
|   </ElCol> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { ref, watch, computed } from 'vue'; | ||||
| import type { PropType } from 'vue'; | ||||
| 
 | ||||
| import * as PropertyApi from '#/api/mall/product/property'; | ||||
| import type { MallPropertyApi } from '#/api/mall/product/property'; | ||||
| import { | ||||
|   ElMessage, | ||||
|   ElCol, | ||||
|   ElTag, | ||||
|   ElText, | ||||
|   ElButton, | ||||
|   ElDivider, | ||||
|   ElSpace, | ||||
| } from 'element-plus'; | ||||
| import { $t } from '#/locales'; | ||||
| import type { PropertyAndValues } from './model'; | ||||
| 
 | ||||
| defineOptions({ name: 'ProductAttributes' }); | ||||
| 
 | ||||
| const inputValue = ref(''); // 输入框值 | ||||
| const attributeIndex = ref<number | null>(null); // 获取焦点时记录当前属性项的index | ||||
| // 输入框显隐控制 | ||||
| const inputVisible = computed(() => (index: number) => { | ||||
|   if (attributeIndex.value === null) return false; | ||||
|   if (attributeIndex.value === index) return true; | ||||
| }); | ||||
| const inputRef = ref<any[]>([]); //标签输入框Ref | ||||
| /** 解决 ref 在 v-for 中的获取问题*/ | ||||
| const setInputRef = (el: any) => { | ||||
|   if (el === null || typeof el === 'undefined') return; | ||||
|   // 如果不存在 id 相同的元素才添加 | ||||
|   if ( | ||||
|     !inputRef.value.some( | ||||
|       (item) => item.inputRef?.attributes.id === el.inputRef?.attributes.id, | ||||
|     ) | ||||
|   ) { | ||||
|     inputRef.value.push(el); | ||||
|   } | ||||
| }; | ||||
| const attributeList = ref<PropertyAndValues[]>([]); // 商品属性列表 | ||||
| const attributeOptions = ref([] as MallPropertyApi.PropertyValue[]); // 商品属性名称下拉框 | ||||
| const props = defineProps({ | ||||
|   propertyList: { | ||||
|     type: Array as PropType<PropertyAndValues[]>, | ||||
|     default: () => [], | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| watch( | ||||
|   () => props.propertyList, | ||||
|   (data) => { | ||||
|     if (!data) return; | ||||
|     attributeList.value = data as any; | ||||
|   }, | ||||
|   { | ||||
|     deep: true, | ||||
|     immediate: true, | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| /** 删除属性值*/ | ||||
| const handleCloseValue = (index: number, valueIndex: number) => { | ||||
|   if (index < attributeList.value.length) { | ||||
|     const values = attributeList.value[index]!.values as any[]; | ||||
|     values.splice(valueIndex, 1); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 删除属性*/ | ||||
| const handleCloseProperty = (index: number) => { | ||||
|   if (index < attributeList.value.length) { | ||||
|     attributeList.value.splice(index, 1); | ||||
|     emit('success', attributeList.value); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 显示输入框并获取焦点 */ | ||||
| const showInput = async (index: number) => { | ||||
|   if (index < attributeList.value.length) { | ||||
|     attributeIndex.value = index; | ||||
|     inputRef.value[index].focus(); | ||||
|     // 获取属性下拉选项 | ||||
|     await getAttributeOptions(attributeList.value[index]!.id); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 输入框失去焦点或点击回车时触发 */ | ||||
| const emit = defineEmits(['success']); // 定义 success 事件,用于操作成功后的回调 | ||||
| const handleInputConfirm = async (index: number, propertyId: number) => { | ||||
|   if (inputValue.value && index < attributeList.value.length) { | ||||
|     // 1. 重复添加校验 | ||||
|     const values = attributeList.value[index]!.values as any[]; | ||||
|     if (values.find((item) => item.name === inputValue.value)) { | ||||
|       ElMessage.warning('已存在相同属性值,请重试'); | ||||
|       attributeIndex.value = null; | ||||
|       inputValue.value = ''; | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 2.1 情况一:属性值已存在,则直接使用并结束 | ||||
|     const existValue = attributeOptions.value.find( | ||||
|       (item) => item.name === inputValue.value, | ||||
|     ); | ||||
|     if (existValue) { | ||||
|       attributeIndex.value = null; | ||||
|       inputValue.value = ''; | ||||
|       const values = attributeList.value[index]!.values as any[]; | ||||
|       values.push({ | ||||
|         id: existValue.id!, | ||||
|         name: existValue.name, | ||||
|       }); | ||||
|       emit('success', attributeList.value); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 2.2 情况二:新属性值,则进行保存 | ||||
|     try { | ||||
|       const id = await PropertyApi.createPropertyValue({ | ||||
|         propertyId, | ||||
|         name: inputValue.value, | ||||
|       }); | ||||
|       const values = attributeList.value[index]!.values as any[]; | ||||
|       values.push({ id, name: inputValue.value }); | ||||
|       ElMessage.success($t('common.createSuccess')); | ||||
|       emit('success', attributeList.value); | ||||
|     } catch { | ||||
|       ElMessage.error('添加失败,请重试'); | ||||
|     } | ||||
|   } | ||||
|   attributeIndex.value = null; | ||||
|   inputValue.value = ''; | ||||
| }; | ||||
| 
 | ||||
| /** 获取商品属性下拉选项 */ | ||||
| const getAttributeOptions = async (propertyId: number) => { | ||||
|   attributeOptions.value = | ||||
|     await PropertyApi.getPropertyValueSimpleList(propertyId); | ||||
| }; | ||||
| </script> | ||||
|  |  | |||
|  | @ -1,26 +1,25 @@ | |||
| <script lang="ts" setup> | ||||
| import { ref, watch } from 'vue'; | ||||
| import type { PropType } from 'vue'; | ||||
| 
 | ||||
| import type { MallPropertyApi } from '#/api/mall/product/property'; | ||||
| 
 | ||||
| import { ref, watch } from 'vue'; | ||||
| 
 | ||||
| import { useVbenModal } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { ElMessage } from 'element-plus'; | ||||
| 
 | ||||
| import { useVbenForm } from '#/adapter/form'; | ||||
| import { $t } from '#/locales'; | ||||
| import { getPropertySimpleList } from '#/api/mall/product/property'; | ||||
| import * as PropertyApi from '#/api/mall/product/property'; | ||||
| import type { MallPropertyApi } from '#/api/mall/product/property'; | ||||
| import { $t } from '#/locales'; | ||||
| 
 | ||||
| // 扩展Property接口,添加values属性 | ||||
| interface ExtendedProperty extends MallPropertyApi.Property { | ||||
|   values?: any[]; | ||||
| } | ||||
| 
 | ||||
| const emit = defineEmits(['success']); | ||||
| 
 | ||||
| const attributeList = ref<ExtendedProperty[]>([]); // 商品属性列表 | ||||
| const attributeOptions = ref([] as MallPropertyApi.Property[]); // 商品属性名称下拉框 | ||||
| // 商品属性名称下拉框 | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   propertyList: { | ||||
|  | @ -29,6 +28,10 @@ const props = defineProps({ | |||
|   }, | ||||
| }); | ||||
| 
 | ||||
| const emit = defineEmits(['success']); | ||||
| 
 | ||||
| const attributeList = ref<ExtendedProperty[]>([]); // 商品属性列表 | ||||
| const attributeOptions = ref([] as MallPropertyApi.Property[]); | ||||
| const [Form, formApi] = useVbenForm({ | ||||
|   commonConfig: { | ||||
|     componentProps: { | ||||
|  |  | |||
|  | @ -1,17 +1,23 @@ | |||
| <script lang="ts" setup> | ||||
| import { useVbenForm } from '#/adapter/form'; | ||||
| import { watch, ref } from 'vue'; | ||||
| import { ElMessage, ElSpace } from 'element-plus'; | ||||
| import SkuList from './sku-list.vue'; | ||||
| import { ref, watch } from 'vue'; | ||||
| 
 | ||||
| import { Page, useVbenModal } from '@vben/common-ui'; | ||||
| import ProductPropertyAddForm from './product-property-add-form.vue'; | ||||
| 
 | ||||
| import { ElMessage, ElSpace } from 'element-plus'; | ||||
| 
 | ||||
| import { useVbenForm } from '#/adapter/form'; | ||||
| 
 | ||||
| import { getPropertyList } from './data'; | ||||
| import ProductAttributes from './product-attributes.vue'; | ||||
| import ProductPropertyAddForm from './product-property-add-form.vue'; | ||||
| import SkuList from './sku-list.vue'; | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
|   propFormData: Object; | ||||
| }>(); | ||||
| 
 | ||||
| const emit = defineEmits(['update:activeName']); | ||||
| 
 | ||||
| interface PropertyAndValues { | ||||
|   id: number; | ||||
|   name: string; | ||||
|  | @ -71,7 +77,6 @@ watch( | |||
|   }, | ||||
| ); | ||||
| 
 | ||||
| const emit = defineEmits(['update:activeName']); | ||||
| const validate = async () => { | ||||
|   const { valid } = await formApi.validate(); | ||||
|   if (!valid) { | ||||
|  | @ -80,10 +85,10 @@ const validate = async () => { | |||
|   try { | ||||
|     // 校验通过更新数据 | ||||
|     Object.assign(props.propFormData, formApi.getValues()); | ||||
|   } catch (e) { | ||||
|   } catch (error) { | ||||
|     ElMessage.error('【库存价格】不完善,请填写相关信息'); | ||||
|     emit('update:activeName', 'sku'); | ||||
|     throw e; // 目的截断之后的校验 | ||||
|     throw error; // 目的截断之后的校验 | ||||
|   } | ||||
| }; | ||||
| defineExpose({ validate }); | ||||
|  | @ -214,9 +219,9 @@ watch( | |||
|       </template> | ||||
|       <template #specTypeItem> | ||||
|         <ElSpace direction="vertical" alignment="flex-start"> | ||||
|           <ElButton type="primary" @click="productPropertyAddFormApi.open()" | ||||
|             >添加属性</ElButton | ||||
|           > | ||||
|           <ElButton type="primary" @click="productPropertyAddFormApi.open()"> | ||||
|             添加属性 | ||||
|           </ElButton> | ||||
|           <ProductAttributes | ||||
|             :property-list="propertyList" | ||||
|             @success="generateSkus" | ||||
|  | @ -238,6 +243,6 @@ watch( | |||
|         /> | ||||
|       </template> | ||||
|     </Form> | ||||
|     <ProductPropertyAddFormModal :propertyList="propertyList" /> | ||||
|     <ProductPropertyAddFormModal :property-list="propertyList" /> | ||||
|   </Page> | ||||
| </template> | ||||
|  |  | |||
|  | @ -1,6 +1,636 @@ | |||
| <script lang="ts" setup> | ||||
| import type { PropType } from 'vue'; | ||||
| 
 | ||||
| import type { PropertyAndValues, RuleConfig } from './model'; | ||||
| 
 | ||||
| import type { MallSpuApi } from '#/api/mall/product/spu'; | ||||
| 
 | ||||
| import { ref, watch } from 'vue'; | ||||
| 
 | ||||
| import { formatToFraction, isEmpty } from '@vben/utils'; | ||||
| 
 | ||||
| import { ElInput, ElMessage, ElTable } from 'element-plus'; | ||||
| 
 | ||||
| import UploadImg from '#/components/upload/image-upload.vue'; | ||||
| import { copyValueToTarget } from '#/utils'; | ||||
| 
 | ||||
| defineOptions({ name: 'SkuList' }); | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   propFormData: { | ||||
|     type: Object as PropType<MallSpuApi.Spu>, | ||||
|     default: () => ({}), | ||||
|   }, | ||||
|   propertyList: { | ||||
|     type: Array as PropType<PropertyAndValues[]>, | ||||
|     default: () => [], | ||||
|   }, | ||||
|   ruleConfig: { | ||||
|     type: Array as PropType<RuleConfig[]>, | ||||
|     default: () => [], | ||||
|   }, | ||||
|   isBatch: { | ||||
|     type: Boolean, | ||||
|     default: false, | ||||
|   }, // 是否作为批量操作组件 | ||||
|   isComponent: { | ||||
|     type: Boolean, | ||||
|     default: false, | ||||
|   }, // 是否作为组件 | ||||
|   isActivityComponent: { | ||||
|     type: Boolean, | ||||
|     default: false, | ||||
|   }, // 是否作为活动组件 | ||||
| }); | ||||
| 
 | ||||
| const emit = defineEmits<{ | ||||
|   (e: 'selectionChange', value: MallSpuApi.Sku[]): void; | ||||
| }>(); const formData = ref<MallSpuApi.Spu>(); // 表单数据 | ||||
| const skuList = ref<MallSpuApi.Sku[]>([ | ||||
|   { | ||||
|     price: 0, // 商品价格 | ||||
|     marketPrice: 0, // 市场价 | ||||
|     costPrice: 0, // 成本价 | ||||
|     barCode: '', // 商品条码 | ||||
|     picUrl: '', // 图片地址 | ||||
|     stock: 0, // 库存 | ||||
|     weight: 0, // 商品重量 | ||||
|     volume: 0, // 商品体积 | ||||
|     firstBrokeragePrice: 0, // 一级分销的佣金 | ||||
|     secondBrokeragePrice: 0, // 二级分销的佣金 | ||||
|   }, | ||||
| ]); // 批量添加时的临时数据 | ||||
| 
 | ||||
| /** 批量添加 */ | ||||
| const batchAdd = () => { | ||||
|   validateProperty(); | ||||
|   formData.value!.skus!.forEach((item: MallSpuApi.Sku) => { | ||||
|     copyValueToTarget(item, skuList.value[0]); | ||||
|   }); | ||||
| }; | ||||
| /** 校验商品属性属性值 */ | ||||
| const validateProperty = () => { | ||||
|   // 校验商品属性属性值是否为空,有一个为空都不给过 | ||||
|   const warningInfo = '存在属性属性值为空,请先检查完善属性值后重试!!!'; | ||||
|   for (const item of props.propertyList) { | ||||
|     if (!item.values || isEmpty(item.values)) { | ||||
|       ElMessage.warning(warningInfo); | ||||
|       throw new Error(warningInfo); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| /** 删除 sku */ | ||||
| const deleteSku = (row: MallSpuApi.Sku) => { | ||||
|   const index = formData.value!.skus!.findIndex( | ||||
|     // 直接把列表转成字符串比较 | ||||
|     (sku: MallSpuApi.Sku) => | ||||
|       JSON.stringify(sku.properties) === JSON.stringify(row.properties), | ||||
|   ); | ||||
|   formData.value!.skus!.splice(index, 1); | ||||
| }; | ||||
| const tableHeaders = ref<{ label: string; prop: string; }[]>([]); // 多属性表头 | ||||
| /** | ||||
|  * 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。 | ||||
|  */ | ||||
| const validateSku = () => { | ||||
|   validateProperty(); | ||||
|   let warningInfo = '请检查商品各行相关属性配置,'; | ||||
|   let validate = true; // 默认通过 | ||||
|   for (const sku of formData.value!.skus!) { | ||||
|     // 作为活动组件的校验 | ||||
|     for (const rule of props?.ruleConfig) { | ||||
|       const arg = getValue(sku, rule.name); | ||||
|       if (!rule.rule(arg)) { | ||||
|         validate = false; // 只要有一个不通过则直接不通过 | ||||
|         warningInfo += rule.message; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     // 只要有一个不通过则结束后续的校验 | ||||
|     if (!validate) { | ||||
|       ElMessage.warning(warningInfo); | ||||
|       throw new Error(warningInfo); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| const getValue = (obj: any, arg: string) => { | ||||
|   const keys = arg.split('.'); | ||||
|   let value = obj; | ||||
|   for (const key of keys) { | ||||
|     if (value && typeof value === 'object' && key in value) { | ||||
|       value = value[key]; | ||||
|     } else { | ||||
|       value = undefined; | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   return value; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 选择时触发 | ||||
|  * @param Sku 传递过来的选中的 sku 是一个数组 | ||||
|  */ | ||||
| const handleSelectionChange = (val: MallSpuApi.Sku[]) => { | ||||
|   emit('selectionChange', val); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 将传进来的值赋值给 skuList | ||||
|  */ | ||||
| watch( | ||||
|   () => props.propFormData, | ||||
|   (data) => { | ||||
|     if (!data) return; | ||||
|     formData.value = data; | ||||
|   }, | ||||
|   { | ||||
|     deep: true, | ||||
|     immediate: true, | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| /** 生成表数据 */ | ||||
| const generateTableData = (propertyList: any[]) => { | ||||
|   // 构建数据结构 | ||||
|   const propertyValues = propertyList.map((item) => | ||||
|     item.values.map((v: any) => ({ | ||||
|       propertyId: item.id, | ||||
|       propertyName: item.name, | ||||
|       valueId: v.id, | ||||
|       valueName: v.name, | ||||
|     })), | ||||
|   ); | ||||
|   const buildSkuList = build(propertyValues); | ||||
|   // 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表 | ||||
|   if (!validateData(propertyList)) { | ||||
|     // 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表 | ||||
|     formData.value!.skus = []; | ||||
|   } | ||||
|   if (buildSkuList && buildSkuList.length > 0) { | ||||
|     for (const item of buildSkuList) { | ||||
|       const row = { | ||||
|         properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象 | ||||
|         price: 0, | ||||
|         marketPrice: 0, | ||||
|         costPrice: 0, | ||||
|         barCode: '', | ||||
|         picUrl: '', | ||||
|         stock: 0, | ||||
|         weight: 0, | ||||
|         volume: 0, | ||||
|         firstBrokeragePrice: 0, | ||||
|         secondBrokeragePrice: 0, | ||||
|       }; | ||||
|       // 如果存在属性相同的 sku 则不做处理 | ||||
|       const index = formData.value!.skus!.findIndex( | ||||
|         (sku: MallSpuApi.Sku) => | ||||
|           JSON.stringify(sku.properties) === JSON.stringify(row.properties), | ||||
|       ); | ||||
|       if (index !== -1) { | ||||
|         continue; | ||||
|       } | ||||
|       formData.value!.skus!.push(row); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 生成 skus 前置校验 | ||||
|  */ | ||||
| const validateData = (propertyList: any[]) => { | ||||
|   const skuPropertyIds: number[] = []; | ||||
|   formData.value!.skus!.forEach((sku: MallSpuApi.Sku) => | ||||
|     sku.properties | ||||
|       ?.map((property: any) => property.propertyId) | ||||
|       ?.forEach((propertyId: number) => { | ||||
|         if (!skuPropertyIds.indexOf(propertyId!) === -1) { | ||||
|           skuPropertyIds.push(propertyId!); | ||||
|         } | ||||
|       }), | ||||
|   ); | ||||
|   const propertyIds = propertyList.map((item) => item.id); | ||||
|   return skuPropertyIds.length === propertyIds.length; | ||||
| }; | ||||
| 
 | ||||
| /** 构建所有排列组合 */ | ||||
| const build = ( | ||||
|   propertyValuesList: MallSpuApi.Property[][], | ||||
| ): MallSpuApi.Property[] | MallSpuApi.Property[][] => { | ||||
|   if (!propertyValuesList || propertyValuesList.length === 0) { | ||||
|     return []; | ||||
|   } else if (propertyValuesList.length === 1) { | ||||
|     return propertyValuesList[0] || []; | ||||
|   } else { | ||||
|     const result: MallSpuApi.Property[][] = []; | ||||
|     const rest = build(propertyValuesList.slice(1)); | ||||
|     if (propertyValuesList[0] && Array.isArray(rest)) { | ||||
|       for (let i = 0; i < propertyValuesList[0].length; i++) { | ||||
|         for (const restItem of rest) { | ||||
|           const currentItem = propertyValuesList[0][i]; | ||||
|           // 第一次不是数组结构,后面的都是数组结构 | ||||
|           if (Array.isArray(restItem)) { | ||||
|             result.push([currentItem!, ...restItem]); | ||||
|           } else if (restItem) { | ||||
|             // 确保restItem不是undefined,并进行类型断言 | ||||
|             result.push([currentItem!, restItem as MallSpuApi.Property]); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 监听属性列表,生成相关参数和表头 */ | ||||
| watch( | ||||
|   () => props.propertyList, | ||||
|   (propertyList: PropertyAndValues[]) => { | ||||
|     // 如果不是多规格则结束 | ||||
|     if (!formData.value!.specType) { | ||||
|       return; | ||||
|     } | ||||
|     // 如果当前组件作为批量添加数据使用,则重置表数据 | ||||
|     if (props.isBatch) { | ||||
|       skuList.value = [ | ||||
|         { | ||||
|           price: 0, | ||||
|           marketPrice: 0, | ||||
|           costPrice: 0, | ||||
|           barCode: '', | ||||
|           picUrl: '', | ||||
|           stock: 0, | ||||
|           weight: 0, | ||||
|           volume: 0, | ||||
|           firstBrokeragePrice: 0, | ||||
|           secondBrokeragePrice: 0, | ||||
|         }, | ||||
|       ]; | ||||
|     } | ||||
| 
 | ||||
|     // 判断代理对象是否为空 | ||||
|     if (JSON.stringify(propertyList) === '[]') { | ||||
|       return; | ||||
|     } | ||||
|     // 重置表头 | ||||
|     tableHeaders.value = []; | ||||
|     // 生成表头 | ||||
|     propertyList.forEach((item, index) => { | ||||
|       // name加属性项index区分属性值 | ||||
|       tableHeaders.value.push({ prop: `name${index}`, label: item.name }); | ||||
|     }); | ||||
|     // 如果回显的 sku 属性和添加的属性一致则不处理 | ||||
|     if (validateData(propertyList)) { | ||||
|       return; | ||||
|     } | ||||
|     // 添加新属性没有属性值也不做处理 | ||||
|     if (propertyList.some((item) => !item.values || isEmpty(item.values))) { | ||||
|       return; | ||||
|     } | ||||
|     // 生成 table 数据,即 sku 列表 | ||||
|     generateTableData(propertyList); | ||||
|   }, | ||||
|   { | ||||
|     deep: true, | ||||
|     immediate: true, | ||||
|   }, | ||||
| ); | ||||
| const activitySkuListRef = ref<InstanceType<typeof ElTable>>(); | ||||
| 
 | ||||
| const getSkuTableRef = () => { | ||||
|   return activitySkuListRef.value; | ||||
| }; | ||||
| // 暴露出生成 sku 方法,给添加属性成功时调用 | ||||
| defineExpose({ generateTableData, validateSku, getSkuTableRef }); | ||||
| </script> | ||||
| <template> | ||||
|   <!-- 情况一:添加/修改 --> | ||||
|   <el-table | ||||
|   <ElTable | ||||
|     v-if="!isActivityComponent" | ||||
|     :data="isBatch ? skuList : formData!.skus!" | ||||
|     border | ||||
|     class="tabNumWidth" | ||||
|     max-height="500" | ||||
|     size="small" | ||||
|   > | ||||
|     <el-table-column align="center" label="图片" min-width="120"> | ||||
|       <template #default="{ row }"> | ||||
|         <UploadImg | ||||
|           v-model="row.picUrl" | ||||
|           height="50px" | ||||
|           width="50px" | ||||
|           :show-description="false" | ||||
|         /> | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <template v-if="formData!.specType && !isBatch"> | ||||
|       <!--  根据商品属性动态添加 --> | ||||
|       <el-table-column | ||||
|         v-for="(item, index) in tableHeaders" | ||||
|         :key="index" | ||||
|         :label="item.label" | ||||
|         align="center" | ||||
|         min-width="120" | ||||
|       > | ||||
|         <template #default="{ row }"> | ||||
|           <span style="font-weight: bold; color: #40aaff"> | ||||
|             {{ row.properties?.[index]?.valueName }} | ||||
|           </span> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|     </template> | ||||
|     <el-table-column align="center" label="商品条码" min-width="168"> | ||||
|       <template #default="{ row }"> | ||||
|         <ElInput v-model="row.barCode" class="w-100%" /> | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <el-table-column align="center" label="销售价" min-width="168"> | ||||
|       <template #default="{ row }"> | ||||
|         <el-input-number | ||||
|           v-model="row.price" | ||||
|           :min="0" | ||||
|           :precision="2" | ||||
|           :step="0.1" | ||||
|           class="w-100%" | ||||
|           controls-position="right" | ||||
|         /> | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <el-table-column align="center" label="市场价" min-width="168"> | ||||
|       <template #default="{ row }"> | ||||
|         <el-input-number | ||||
|           v-model="row.marketPrice" | ||||
|           :min="0" | ||||
|           :precision="2" | ||||
|           :step="0.1" | ||||
|           class="w-100%" | ||||
|           controls-position="right" | ||||
|         /> | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <el-table-column align="center" label="成本价" min-width="168"> | ||||
|       <template #default="{ row }"> | ||||
|         <el-input-number | ||||
|           v-model="row.costPrice" | ||||
|           :min="0" | ||||
|           :precision="2" | ||||
|           :step="0.1" | ||||
|           class="w-100%" | ||||
|           controls-position="right" | ||||
|         /> | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <el-table-column align="center" label="库存" min-width="168"> | ||||
|       <template #default="{ row }"> | ||||
|         <el-input-number | ||||
|           v-model="row.stock" | ||||
|           :min="0" | ||||
|           class="w-100%" | ||||
|           controls-position="right" | ||||
|         /> | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <el-table-column align="center" label="重量(kg)" min-width="168"> | ||||
|       <template #default="{ row }"> | ||||
|         <el-input-number | ||||
|           v-model="row.weight" | ||||
|           :min="0" | ||||
|           :precision="2" | ||||
|           :step="0.1" | ||||
|           class="w-100%" | ||||
|           controls-position="right" | ||||
|         /> | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <el-table-column align="center" label="体积(m^3)" min-width="168"> | ||||
|       <template #default="{ row }"> | ||||
|         <el-input-number | ||||
|           v-model="row.volume" | ||||
|           :min="0" | ||||
|           :precision="2" | ||||
|           :step="0.1" | ||||
|           class="w-100%" | ||||
|           controls-position="right" | ||||
|         /> | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <template v-if="formData!.subCommissionType"> | ||||
|       <el-table-column align="center" label="一级返佣(元)" min-width="168"> | ||||
|         <template #default="{ row }"> | ||||
|           <el-input-number | ||||
|             v-model="row.firstBrokeragePrice" | ||||
|             :min="0" | ||||
|             :precision="2" | ||||
|             :step="0.1" | ||||
|             class="w-100%" | ||||
|             controls-position="right" | ||||
|           /> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column align="center" label="二级返佣(元)" min-width="168"> | ||||
|         <template #default="{ row }"> | ||||
|           <el-input-number | ||||
|             v-model="row.secondBrokeragePrice" | ||||
|             :min="0" | ||||
|             :precision="2" | ||||
|             :step="0.1" | ||||
|             class="w-100%" | ||||
|             controls-position="right" | ||||
|           /> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|     </template> | ||||
|     <el-table-column | ||||
|       v-if="formData?.specType" | ||||
|       align="center" | ||||
|       fixed="right" | ||||
|       label="操作" | ||||
|       width="80" | ||||
|     > | ||||
|       <template #default="{ row }"> | ||||
|         <el-button | ||||
|           v-if="isBatch" | ||||
|           link | ||||
|           size="small" | ||||
|           type="primary" | ||||
|           @click="batchAdd" | ||||
|         > | ||||
|           批量添加 | ||||
|         </el-button> | ||||
|         <el-button | ||||
|           v-else | ||||
|           link | ||||
|           size="small" | ||||
|           type="primary" | ||||
|           @click="deleteSku(row)" | ||||
|           > | ||||
| 删除 | ||||
| </el-button> | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|   </ElTable> | ||||
| 
 | ||||
|   <!-- 情况二:作为活动组件 --> | ||||
|   <ElTable | ||||
|     v-if="isActivityComponent" | ||||
|     :data="formData!.skus!" | ||||
|     border | ||||
|     max-height="500" | ||||
|     size="small" | ||||
|     style="width: 99%" | ||||
|   > | ||||
|     <el-table-column v-if="isComponent" type="selection" width="45" /> | ||||
|     <el-table-column align="center" label="图片" min-width="80"> | ||||
|       <template #default="{ row }"> | ||||
|         <el-image :src="row.picUrl" class="h-60px w-60px" /> | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <template v-if="formData!.specType"> | ||||
|       <!--  根据商品属性动态添加 --> | ||||
|       <el-table-column | ||||
|         v-for="(item, index) in tableHeaders" | ||||
|         :key="index" | ||||
|         :label="item.label" | ||||
|         align="center" | ||||
|         min-width="80" | ||||
|       > | ||||
|         <template #default="{ row }"> | ||||
|           <span style="font-weight: bold; color: #40aaff"> | ||||
|             {{ row.properties?.[index]?.valueName }} | ||||
|           </span> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|     </template> | ||||
|     <el-table-column align="center" label="商品条码" min-width="100"> | ||||
|       <template #default="{ row }"> | ||||
|         {{ row.barCode }} | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <el-table-column align="center" label="销售价(元)" min-width="80"> | ||||
|       <template #default="{ row }"> | ||||
|         {{ formatToFraction(row.price) }} | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <el-table-column align="center" label="市场价(元)" min-width="80"> | ||||
|       <template #default="{ row }"> | ||||
|         {{ formatToFraction(row.marketPrice) }} | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <el-table-column align="center" label="成本价(元)" min-width="80"> | ||||
|       <template #default="{ row }"> | ||||
|         {{ formatToFraction(row.costPrice) }} | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <el-table-column align="center" label="库存" min-width="80"> | ||||
|       <template #default="{ row }"> | ||||
|         {{ row.stock }} | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <!--  方便扩展每个活动配置的属性不一样  --> | ||||
|     <slot name="extension"></slot> | ||||
|   </ElTable> | ||||
| </template>.includes(propertyId!)) { | ||||
|           skuPropertyIds.push(propertyId!); | ||||
|         } | ||||
|       }), | ||||
|   ); | ||||
|   const propertyIds = propertyList.map((item) => item.id); | ||||
|   return skuPropertyIds.length === propertyIds.length; | ||||
| }; | ||||
| 
 | ||||
| /** 构建所有排列组合 */ | ||||
| const build = ( | ||||
|   propertyValuesList: MallSpuApi.Property[][], | ||||
| ): MallSpuApi.Property[] | MallSpuApi.Property[][] => { | ||||
|   if (!propertyValuesList || propertyValuesList.length === 0) { | ||||
|     return []; | ||||
|   } else if (propertyValuesList.length === 1) { | ||||
|     return propertyValuesList[0] || []; | ||||
|   } else { | ||||
|     const result: MallSpuApi.Property[][] = []; | ||||
|     const rest = build(propertyValuesList.slice(1)); | ||||
|     if (propertyValuesList[0] && Array.isArray(rest)) { | ||||
|       for (let i = 0; i < propertyValuesList[0].length; i++) { | ||||
|         for (const restItem of rest) { | ||||
|           const currentItem = propertyValuesList[0][i]; | ||||
|           // 第一次不是数组结构,后面的都是数组结构 | ||||
|           if (Array.isArray(restItem)) { | ||||
|             result.push([currentItem!, ...restItem]); | ||||
|           } else if (restItem) { | ||||
|             // 确保restItem不是undefined,并进行类型断言 | ||||
|             result.push([currentItem!, restItem as MallSpuApi.Property]); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 监听属性列表,生成相关参数和表头 */ | ||||
| watch( | ||||
|   () => props.propertyList, | ||||
|   (propertyList: PropertyAndValues[]) => { | ||||
|     // 如果不是多规格则结束 | ||||
|     if (!formData.value!.specType) { | ||||
|       return; | ||||
|     } | ||||
|     // 如果当前组件作为批量添加数据使用,则重置表数据 | ||||
|     if (props.isBatch) { | ||||
|       skuList.value = [ | ||||
|         { | ||||
|           price: 0, | ||||
|           marketPrice: 0, | ||||
|           costPrice: 0, | ||||
|           barCode: '', | ||||
|           picUrl: '', | ||||
|           stock: 0, | ||||
|           weight: 0, | ||||
|           volume: 0, | ||||
|           firstBrokeragePrice: 0, | ||||
|           secondBrokeragePrice: 0, | ||||
|         }, | ||||
|       ]; | ||||
|     } | ||||
| 
 | ||||
|     // 判断代理对象是否为空 | ||||
|     if (JSON.stringify(propertyList) === '[]') { | ||||
|       return; | ||||
|     } | ||||
|     // 重置表头 | ||||
|     tableHeaders.value = []; | ||||
|     // 生成表头 | ||||
|     propertyList.forEach((item, index) => { | ||||
|       // name加属性项index区分属性值 | ||||
|       tableHeaders.value.push({ prop: `name${index}`, label: item.name }); | ||||
|     }); | ||||
|     // 如果回显的 sku 属性和添加的属性一致则不处理 | ||||
|     if (validateData(propertyList)) { | ||||
|       return; | ||||
|     } | ||||
|     // 添加新属性没有属性值也不做处理 | ||||
|     if (propertyList.some((item) => !item.values || isEmpty(item.values))) { | ||||
|       return; | ||||
|     } | ||||
|     // 生成 table 数据,即 sku 列表 | ||||
|     generateTableData(propertyList); | ||||
|   }, | ||||
|   { | ||||
|     deep: true, | ||||
|     immediate: true, | ||||
|   }, | ||||
| ); | ||||
| const activitySkuListRef = ref<InstanceType<typeof ElTable>>(); | ||||
| 
 | ||||
| const getSkuTableRef = () => { | ||||
|   return activitySkuListRef.value; | ||||
| }; | ||||
| // 暴露出生成 sku 方法,给添加属性成功时调用 | ||||
| defineExpose({ generateTableData, validateSku, getSkuTableRef }); | ||||
| </script> | ||||
| <template> | ||||
|   <!-- 情况一:添加/修改 --> | ||||
|   <ElTable | ||||
|     v-if="!isActivityComponent" | ||||
|     :data="isBatch ? skuList : formData!.skus!" | ||||
|     border | ||||
|  | @ -162,10 +792,10 @@ | |||
|         > | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|   </el-table> | ||||
|   </ElTable> | ||||
| 
 | ||||
|   <!-- 情况二:作为活动组件 --> | ||||
|   <el-table | ||||
|   <ElTable | ||||
|     v-if="isActivityComponent" | ||||
|     :data="formData!.skus!" | ||||
|     border | ||||
|  | @ -222,306 +852,5 @@ | |||
|     </el-table-column> | ||||
|     <!--  方便扩展每个活动配置的属性不一样  --> | ||||
|     <slot name="extension"></slot> | ||||
|   </el-table> | ||||
|   </ElTable> | ||||
| </template> | ||||
| <script lang="ts" setup> | ||||
| import { copyValueToTarget } from '#/utils'; | ||||
| import { formatToFraction, isEmpty } from '@vben/utils'; | ||||
| import type { PropertyAndValues, RuleConfig } from './model'; | ||||
| import UploadImg from '#/components/upload/image-upload.vue'; | ||||
| import { ElTable, ElInput, ElMessage } from 'element-plus'; | ||||
| import type { MallSpuApi } from '#/api/mall/product/spu'; | ||||
| import { ref, watch } from 'vue'; | ||||
| import type { PropType } from 'vue'; | ||||
| 
 | ||||
| defineOptions({ name: 'SkuList' }); | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   propFormData: { | ||||
|     type: Object as PropType<MallSpuApi.Spu>, | ||||
|     default: () => ({}), | ||||
|   }, | ||||
|   propertyList: { | ||||
|     type: Array as PropType<PropertyAndValues[]>, | ||||
|     default: () => [], | ||||
|   }, | ||||
|   ruleConfig: { | ||||
|     type: Array as PropType<RuleConfig[]>, | ||||
|     default: () => [], | ||||
|   }, | ||||
|   isBatch: { | ||||
|     type: Boolean, | ||||
|     default: false, | ||||
|   }, // 是否作为批量操作组件 | ||||
|   isComponent: { | ||||
|     type: Boolean, | ||||
|     default: false, | ||||
|   }, // 是否作为组件 | ||||
|   isActivityComponent: { | ||||
|     type: Boolean, | ||||
|     default: false, | ||||
|   }, // 是否作为活动组件 | ||||
| }); | ||||
| 
 | ||||
| const formData = ref<MallSpuApi.Spu>(); // 表单数据 | ||||
| const skuList = ref<MallSpuApi.Sku[]>([ | ||||
|   { | ||||
|     price: 0, // 商品价格 | ||||
|     marketPrice: 0, // 市场价 | ||||
|     costPrice: 0, // 成本价 | ||||
|     barCode: '', // 商品条码 | ||||
|     picUrl: '', // 图片地址 | ||||
|     stock: 0, // 库存 | ||||
|     weight: 0, // 商品重量 | ||||
|     volume: 0, // 商品体积 | ||||
|     firstBrokeragePrice: 0, // 一级分销的佣金 | ||||
|     secondBrokeragePrice: 0, // 二级分销的佣金 | ||||
|   }, | ||||
| ]); // 批量添加时的临时数据 | ||||
| 
 | ||||
| /** 批量添加 */ | ||||
| const batchAdd = () => { | ||||
|   validateProperty(); | ||||
|   formData.value!.skus!.forEach((item: MallSpuApi.Sku) => { | ||||
|     copyValueToTarget(item, skuList.value[0]); | ||||
|   }); | ||||
| }; | ||||
| /** 校验商品属性属性值 */ | ||||
| const validateProperty = () => { | ||||
|   // 校验商品属性属性值是否为空,有一个为空都不给过 | ||||
|   const warningInfo = '存在属性属性值为空,请先检查完善属性值后重试!!!'; | ||||
|   for (const item of props.propertyList) { | ||||
|     if (!item.values || isEmpty(item.values)) { | ||||
|       ElMessage.warning(warningInfo); | ||||
|       throw new Error(warningInfo); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| /** 删除 sku */ | ||||
| const deleteSku = (row: MallSpuApi.Sku) => { | ||||
|   const index = formData.value!.skus!.findIndex( | ||||
|     // 直接把列表转成字符串比较 | ||||
|     (sku: MallSpuApi.Sku) => | ||||
|       JSON.stringify(sku.properties) === JSON.stringify(row.properties), | ||||
|   ); | ||||
|   formData.value!.skus!.splice(index, 1); | ||||
| }; | ||||
| const tableHeaders = ref<{ prop: string; label: string }[]>([]); // 多属性表头 | ||||
| /** | ||||
|  * 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。 | ||||
|  */ | ||||
| const validateSku = () => { | ||||
|   validateProperty(); | ||||
|   let warningInfo = '请检查商品各行相关属性配置,'; | ||||
|   let validate = true; // 默认通过 | ||||
|   for (const sku of formData.value!.skus!) { | ||||
|     // 作为活动组件的校验 | ||||
|     for (const rule of props?.ruleConfig) { | ||||
|       const arg = getValue(sku, rule.name); | ||||
|       if (!rule.rule(arg)) { | ||||
|         validate = false; // 只要有一个不通过则直接不通过 | ||||
|         warningInfo += rule.message; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     // 只要有一个不通过则结束后续的校验 | ||||
|     if (!validate) { | ||||
|       ElMessage.warning(warningInfo); | ||||
|       throw new Error(warningInfo); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| const getValue = (obj: any, arg: string) => { | ||||
|   const keys = arg.split('.'); | ||||
|   let value = obj; | ||||
|   for (const key of keys) { | ||||
|     if (value && typeof value === 'object' && key in value) { | ||||
|       value = value[key]; | ||||
|     } else { | ||||
|       value = undefined; | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   return value; | ||||
| }; | ||||
| 
 | ||||
| const emit = defineEmits<{ | ||||
|   (e: 'selectionChange', value: MallSpuApi.Sku[]): void; | ||||
| }>(); | ||||
| /** | ||||
|  * 选择时触发 | ||||
|  * @param Sku 传递过来的选中的 sku 是一个数组 | ||||
|  */ | ||||
| const handleSelectionChange = (val: MallSpuApi.Sku[]) => { | ||||
|   emit('selectionChange', val); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 将传进来的值赋值给 skuList | ||||
|  */ | ||||
| watch( | ||||
|   () => props.propFormData, | ||||
|   (data) => { | ||||
|     if (!data) return; | ||||
|     formData.value = data; | ||||
|   }, | ||||
|   { | ||||
|     deep: true, | ||||
|     immediate: true, | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| /** 生成表数据 */ | ||||
| const generateTableData = (propertyList: any[]) => { | ||||
|   // 构建数据结构 | ||||
|   const propertyValues = propertyList.map((item) => | ||||
|     item.values.map((v: any) => ({ | ||||
|       propertyId: item.id, | ||||
|       propertyName: item.name, | ||||
|       valueId: v.id, | ||||
|       valueName: v.name, | ||||
|     })), | ||||
|   ); | ||||
|   const buildSkuList = build(propertyValues); | ||||
|   // 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表 | ||||
|   if (!validateData(propertyList)) { | ||||
|     // 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表 | ||||
|     formData.value!.skus = []; | ||||
|   } | ||||
|   if (buildSkuList && buildSkuList.length > 0) { | ||||
|     for (const item of buildSkuList) { | ||||
|       const row = { | ||||
|         properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象 | ||||
|         price: 0, | ||||
|         marketPrice: 0, | ||||
|         costPrice: 0, | ||||
|         barCode: '', | ||||
|         picUrl: '', | ||||
|         stock: 0, | ||||
|         weight: 0, | ||||
|         volume: 0, | ||||
|         firstBrokeragePrice: 0, | ||||
|         secondBrokeragePrice: 0, | ||||
|       }; | ||||
|       // 如果存在属性相同的 sku 则不做处理 | ||||
|       const index = formData.value!.skus!.findIndex( | ||||
|         (sku: MallSpuApi.Sku) => | ||||
|           JSON.stringify(sku.properties) === JSON.stringify(row.properties), | ||||
|       ); | ||||
|       if (index !== -1) { | ||||
|         continue; | ||||
|       } | ||||
|       formData.value!.skus!.push(row); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 生成 skus 前置校验 | ||||
|  */ | ||||
| const validateData = (propertyList: any[]) => { | ||||
|   const skuPropertyIds: number[] = []; | ||||
|   formData.value!.skus!.forEach((sku: MallSpuApi.Sku) => | ||||
|     sku.properties | ||||
|       ?.map((property: any) => property.propertyId) | ||||
|       ?.forEach((propertyId: number) => { | ||||
|         if (skuPropertyIds.indexOf(propertyId!) === -1) { | ||||
|           skuPropertyIds.push(propertyId!); | ||||
|         } | ||||
|       }), | ||||
|   ); | ||||
|   const propertyIds = propertyList.map((item) => item.id); | ||||
|   return skuPropertyIds.length === propertyIds.length; | ||||
| }; | ||||
| 
 | ||||
| /** 构建所有排列组合 */ | ||||
| const build = ( | ||||
|   propertyValuesList: MallSpuApi.Property[][], | ||||
| ): MallSpuApi.Property[] | MallSpuApi.Property[][] => { | ||||
|   if (!propertyValuesList || propertyValuesList.length === 0) { | ||||
|     return []; | ||||
|   } else if (propertyValuesList.length === 1) { | ||||
|     return propertyValuesList[0] || []; | ||||
|   } else { | ||||
|     const result: MallSpuApi.Property[][] = []; | ||||
|     const rest = build(propertyValuesList.slice(1)); | ||||
|     if (propertyValuesList[0] && Array.isArray(rest)) { | ||||
|       for (let i = 0; i < propertyValuesList[0].length; i++) { | ||||
|         for (let j = 0; j < rest.length; j++) { | ||||
|           const currentItem = propertyValuesList[0][i]; | ||||
|           const restItem = rest[j]; | ||||
|           // 第一次不是数组结构,后面的都是数组结构 | ||||
|           if (Array.isArray(restItem)) { | ||||
|             result.push([currentItem!, ...restItem]); | ||||
|           } else if (restItem) { | ||||
|             // 确保restItem不是undefined,并进行类型断言 | ||||
|             result.push([currentItem!, restItem as MallSpuApi.Property]); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 监听属性列表,生成相关参数和表头 */ | ||||
| watch( | ||||
|   () => props.propertyList, | ||||
|   (propertyList: PropertyAndValues[]) => { | ||||
|     // 如果不是多规格则结束 | ||||
|     if (!formData.value!.specType) { | ||||
|       return; | ||||
|     } | ||||
|     // 如果当前组件作为批量添加数据使用,则重置表数据 | ||||
|     if (props.isBatch) { | ||||
|       skuList.value = [ | ||||
|         { | ||||
|           price: 0, | ||||
|           marketPrice: 0, | ||||
|           costPrice: 0, | ||||
|           barCode: '', | ||||
|           picUrl: '', | ||||
|           stock: 0, | ||||
|           weight: 0, | ||||
|           volume: 0, | ||||
|           firstBrokeragePrice: 0, | ||||
|           secondBrokeragePrice: 0, | ||||
|         }, | ||||
|       ]; | ||||
|     } | ||||
| 
 | ||||
|     // 判断代理对象是否为空 | ||||
|     if (JSON.stringify(propertyList) === '[]') { | ||||
|       return; | ||||
|     } | ||||
|     // 重置表头 | ||||
|     tableHeaders.value = []; | ||||
|     // 生成表头 | ||||
|     propertyList.forEach((item, index) => { | ||||
|       // name加属性项index区分属性值 | ||||
|       tableHeaders.value.push({ prop: `name${index}`, label: item.name }); | ||||
|     }); | ||||
|     // 如果回显的 sku 属性和添加的属性一致则不处理 | ||||
|     if (validateData(propertyList)) { | ||||
|       return; | ||||
|     } | ||||
|     // 添加新属性没有属性值也不做处理 | ||||
|     if (propertyList.some((item) => !item.values || isEmpty(item.values))) { | ||||
|       return; | ||||
|     } | ||||
|     // 生成 table 数据,即 sku 列表 | ||||
|     generateTableData(propertyList); | ||||
|   }, | ||||
|   { | ||||
|     deep: true, | ||||
|     immediate: true, | ||||
|   }, | ||||
| ); | ||||
| const activitySkuListRef = ref<InstanceType<typeof ElTable>>(); | ||||
| 
 | ||||
| const getSkuTableRef = () => { | ||||
|   return activitySkuListRef.value; | ||||
| }; | ||||
| // 暴露出生成 sku 方法,给添加属性成功时调用 | ||||
| defineExpose({ generateTableData, validateSku, getSkuTableRef }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -1,16 +1,19 @@ | |||
| <script lang="ts" setup> | ||||
| import { Page } from '@vben/common-ui'; | ||||
| import { onMounted, ref, unref } from 'vue'; | ||||
| import { cloneDeep } from '@vben/utils'; | ||||
| import type { MallSpuApi } from '#/api/mall/product/spu'; | ||||
| import { useRouter, useRoute } from 'vue-router'; | ||||
| import { formatToFraction, convertToInteger } from '@vben/utils'; | ||||
| import * as ProductSpuApi from '#/api/mall/product/spu'; | ||||
| 
 | ||||
| import { onMounted, ref, unref } from 'vue'; | ||||
| import { useRoute, useRouter } from 'vue-router'; | ||||
| 
 | ||||
| import { Page } from '@vben/common-ui'; | ||||
| import { cloneDeep, convertToInteger, formatToFraction } from '@vben/utils'; | ||||
| 
 | ||||
| import { ElMessage } from 'element-plus'; | ||||
| 
 | ||||
| import InfoForm from '../components/info-form.vue'; | ||||
| import * as ProductSpuApi from '#/api/mall/product/spu'; | ||||
| 
 | ||||
| import DeliveryForm from '../components/delivery-form.vue'; | ||||
| import DescriptionForm from '../components/description-form.vue'; | ||||
| import InfoForm from '../components/info-form.vue'; | ||||
| import OtherForm from '../components/other-form.vue'; | ||||
| import SkuForm from '../components/sku-form.vue'; | ||||
| 
 | ||||
|  | @ -115,12 +118,12 @@ const submitForm = async () => { | |||
|     // 校验都通过后提交表单 | ||||
|     const data = deepCopyFormData as MallSpuApi.Spu; | ||||
|     const id = params.id as unknown as number; | ||||
|     if (!id) { | ||||
|       await ProductSpuApi.createSpu(data); | ||||
|       ElMessage.success('创建成功'); | ||||
|     } else { | ||||
|     if (id) { | ||||
|       await ProductSpuApi.updateSpu(data); | ||||
|       ElMessage.success('更新成功'); | ||||
|     } else { | ||||
|       await ProductSpuApi.createSpu(data); | ||||
|       ElMessage.success('创建成功'); | ||||
|     } | ||||
|     close(); | ||||
|   } finally { | ||||
|  | @ -144,43 +147,43 @@ onMounted(async () => { | |||
|     <ElTabs v-model="activeTab"> | ||||
|       <ElTabPane label="基础设置" name="info"> | ||||
|         <InfoForm | ||||
|           :propFormData="formData" | ||||
|           v-model:activeName="activeName" | ||||
|           :prop-form-data="formData" | ||||
|           v-model:active-name="activeName" | ||||
|           ref="infoRef" | ||||
|         /> | ||||
|       </ElTabPane> | ||||
|       <ElTabPane label="价格库存" name="sku"> | ||||
|         <SkuForm | ||||
|           :propFormData="formData" | ||||
|           v-model:activeName="activeName" | ||||
|           :prop-form-data="formData" | ||||
|           v-model:active-name="activeName" | ||||
|           ref="skuRef" | ||||
|         /> | ||||
|       </ElTabPane> | ||||
|       <ElTabPane label="物流设置" name="delivery"> | ||||
|         <DeliveryForm | ||||
|           :propFormData="formData" | ||||
|           v-model:activeName="activeName" | ||||
|           :prop-form-data="formData" | ||||
|           v-model:active-name="activeName" | ||||
|           ref="deliveryRef" | ||||
|         /> | ||||
|       </ElTabPane> | ||||
|       <ElTabPane label="商品详情" name="description"> | ||||
|         <DescriptionForm | ||||
|           :propFormData="formData" | ||||
|           v-model:activeName="activeName" | ||||
|           :prop-form-data="formData" | ||||
|           v-model:active-name="activeName" | ||||
|           ref="descriptionRef" | ||||
|         /> | ||||
|       </ElTabPane> | ||||
|       <ElTabPane label="其它设置" name="other"> | ||||
|         <OtherForm | ||||
|           :propFormData="formData" | ||||
|           v-model:activeName="activeName" | ||||
|           :prop-form-data="formData" | ||||
|           v-model:active-name="activeName" | ||||
|           ref="otherRef" | ||||
|         /> | ||||
|       </ElTabPane> | ||||
|     </ElTabs> | ||||
|     <ElButton type="primary" :loading="formLoading" @click="submitForm" | ||||
|       >保存</ElButton | ||||
|     > | ||||
|     <ElButton type="primary" :loading="formLoading" @click="submitForm"> | ||||
|       保存 | ||||
|     </ElButton> | ||||
|     <ElButton @click="close">返回</ElButton> | ||||
|   </Page> | ||||
| </template> | ||||
|  |  | |||
|  | @ -76,6 +76,7 @@ async function onDelete(row: any) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该字典数据吗?'); | ||||
|   await deleteDictDataList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -71,6 +71,7 @@ async function onDelete(row: SystemDictTypeApi.DictType) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该字典类型吗?'); | ||||
|   await deleteDictTypeList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -62,6 +62,7 @@ async function onDelete(row: SystemMailAccountApi.MailAccount) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该邮箱账号吗?'); | ||||
|   await deleteMailAccountList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -82,6 +82,7 @@ async function onDelete(row: SystemMailTemplateApi.MailTemplate) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该邮件模板吗?'); | ||||
|   await deleteMailTemplateList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -63,6 +63,7 @@ async function onDelete(row: SystemNoticeApi.Notice) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该公告吗?'); | ||||
|   await deleteNoticeList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -87,6 +87,7 @@ async function onDeleteBatch() { | |||
|   }); | ||||
|   try { | ||||
|     await deleteNotifyTemplateList(checkedIds.value); | ||||
|     checkedIds.value = []; | ||||
|     loadingInstance.close(); | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     onRefresh(); | ||||
|  |  | |||
|  | @ -62,6 +62,7 @@ async function onDelete(row: SystemOAuth2ClientApi.OAuth2Client) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该 OAuth2 客户端吗?'); | ||||
|   await deleteOAuth2ClientList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -69,6 +69,7 @@ async function onDelete(row: SystemPostApi.Post) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该岗位吗?'); | ||||
|   await deletePostList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -83,6 +83,7 @@ async function onDelete(row: SystemRoleApi.Role) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该角色吗?'); | ||||
|   await deleteRoleList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -69,6 +69,7 @@ async function onDelete(row: SystemSmsChannelApi.SmsChannel) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该短信渠道吗?'); | ||||
|   await deleteSmsChannelList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -80,6 +80,7 @@ async function onDelete(row: SystemSmsTemplateApi.SmsTemplate) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该短信模板吗?'); | ||||
|   await deleteSmsTemplateList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -62,6 +62,7 @@ async function onDelete(row: SystemSocialClientApi.SocialClient) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该社交客户端吗?'); | ||||
|   await deleteSocialClientList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -81,6 +81,7 @@ async function onDelete(row: SystemTenantApi.Tenant) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该租户吗?'); | ||||
|   await deleteTenantList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -62,6 +62,7 @@ async function onDelete(row: SystemTenantPackageApi.TenantPackage) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该租户套餐吗?'); | ||||
|   await deleteTenantPackageList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
|  | @ -100,6 +100,7 @@ async function onDelete(row: SystemUserApi.User) { | |||
| async function onDeleteBatch() { | ||||
|   await confirm('确定要批量删除该用户吗?'); | ||||
|   await deleteUserList(checkedIds.value); | ||||
|   checkedIds.value = []; | ||||
|   ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|   onRefresh(); | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 芋道源码
						芋道源码