|
|
@@ -1,5 +1,5 @@
|
|
|
<script setup lang="tsx">
|
|
|
-import { onMounted, reactive, ref } from 'vue'
|
|
|
+import { onMounted, reactive, ref, useTemplateRef, unref } from 'vue'
|
|
|
import { ContentWrap } from '@/components/ContentWrap'
|
|
|
import {
|
|
|
ElInput,
|
|
|
@@ -14,15 +14,30 @@ import {
|
|
|
ElMessage,
|
|
|
ElMessageBox,
|
|
|
ElSelect,
|
|
|
- ElOption
|
|
|
+ ElOption,
|
|
|
+ ElRadioGroup,
|
|
|
+ ElRadioButton,
|
|
|
+ ElTag,
|
|
|
+ ElTableColumn,
|
|
|
+ ElTable,
|
|
|
+ ElDivider
|
|
|
} from 'element-plus'
|
|
|
import { Editor, EditorExpose } from '@/components/Editor'
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
import { goodsData, attrsValue } from '@/api/goods/types'
|
|
|
-import { getProductCategory, addProduct, getProductDetail, putProduct } from '@/api/goods'
|
|
|
+import {
|
|
|
+ getProductCategory,
|
|
|
+ addProduct,
|
|
|
+ getProductDetail,
|
|
|
+ putProduct,
|
|
|
+ getAttrSelectList
|
|
|
+} from '@/api/goods'
|
|
|
+import { getProductRule } from '@/api/goods'
|
|
|
+import { productRule, productRuleValue, AttrBaseItem } from '@/api/goods/types'
|
|
|
import { UpImgButtom } from '@/components/UpFile'
|
|
|
import { getStoreList } from '@/api/store'
|
|
|
import { isArray } from 'lodash-es'
|
|
|
+import { TableImage } from '@/components/tableImage'
|
|
|
const pageTitle = ref('添加')
|
|
|
const { params } = useRoute()
|
|
|
const { push } = useRouter()
|
|
|
@@ -62,22 +77,48 @@ const attrs_value = reactive<
|
|
|
ot_price: 0,
|
|
|
cost: 0
|
|
|
})
|
|
|
+
|
|
|
+/**
|
|
|
+ * 创建并返回一个字段验证规则对象
|
|
|
+ *
|
|
|
+ * @param {string} field - 需要验证的字段名称
|
|
|
+ * @param {string} errorMessage - 当验证失败时显示的错误信息
|
|
|
+ * @returns {Object} 包含验证规则的对象,包括错误信息、是否必填、及自定义验证器
|
|
|
+ */
|
|
|
const validateField = (field, errorMessage) => {
|
|
|
+ // 返回一个对象,定义了验证规则
|
|
|
return {
|
|
|
+ // 错误信息,在验证失败时展示
|
|
|
message: errorMessage,
|
|
|
+ // 标记字段为必填
|
|
|
required: true,
|
|
|
+ // 自定义验证器,接收三个参数:规则、数据、及回调函数
|
|
|
validator: (_, __, callback) => {
|
|
|
+ // 检查指定字段在属性值对象中是否存在
|
|
|
if (!attrs_value[field]) {
|
|
|
+ // 如果字段不存在,通过回调函数返回一个错误对象,包含错误信息
|
|
|
callback(new Error(errorMessage))
|
|
|
} else {
|
|
|
+ // 如果字段存在,调用回调函数但不传递错误,表示验证通过
|
|
|
callback()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * 根据规格类型获取对应的验证规则
|
|
|
+ *
|
|
|
+ * 此函数用于根据不同的商品规格类型返回相应的验证规则它接受一个规格类型参数(specType),
|
|
|
+ * 并根据此参数的值返回不同的验证规则对象如果specType的值是0,则返回包含商品图片、库存、售价和成本价的验证规则;
|
|
|
+ * 如果specType的值是1,则返回一个空对象表示没有验证规则;如果specType的值不是0或1,则在控制台输出警告信息并返回一个空对象
|
|
|
+ *
|
|
|
+ * @param {number} specType - 商品的规格类型,用于确定返回哪种验证规则
|
|
|
+ * @returns {Object} - 返回对应规格类型的验证规则对象
|
|
|
+ */
|
|
|
const getRulesForSpecType = (specType) => {
|
|
|
if (specType === 0) {
|
|
|
+ // 当规格类型为0时,返回包含商品图片、库存、售价和成本价验证规则的对象
|
|
|
return {
|
|
|
sutimage: [validateField('image', '请选择商品图片')],
|
|
|
stock: [validateField('stock', '请填写库存')],
|
|
|
@@ -85,8 +126,10 @@ const getRulesForSpecType = (specType) => {
|
|
|
cost: [validateField('cost', '请填写成本价')]
|
|
|
}
|
|
|
} else if (specType === 1) {
|
|
|
+ // 当规格类型为1时,返回一个空对象表示没有验证规则
|
|
|
return {}
|
|
|
} else {
|
|
|
+ // 当规格类型不是0或1时,输出警告信息并返回一个空对象
|
|
|
console.warn('Unknown spec_type:', specType)
|
|
|
return {}
|
|
|
}
|
|
|
@@ -116,7 +159,7 @@ const rules = reactive({
|
|
|
...getRulesForSpecType(formData.spec_type)
|
|
|
})
|
|
|
const formRef = ref<FormInstance>()
|
|
|
-
|
|
|
+// 分类查询功能
|
|
|
const cateIdsProps = {
|
|
|
checkStrictly: false,
|
|
|
// emitPath: false,
|
|
|
@@ -143,39 +186,71 @@ const cateIdsProps = {
|
|
|
}
|
|
|
|
|
|
const editorRef = ref<typeof Editor & EditorExpose>()
|
|
|
+/**
|
|
|
+ * 递归地展平类别ID数组
|
|
|
+ * 此函数的目的是将一个包含数字和数字数组的数组转换成一个不重复的数字集合
|
|
|
+ * 这在处理商品分类ID时特别有用,其中子分类ID需要被展平以便于后续处理
|
|
|
+ *
|
|
|
+ * @param items - 一个数组,其元素可以是数字或数字数组,代表类别ID
|
|
|
+ * @returns 返回一个Set,包含所有展平后的类别ID,不包含重复项
|
|
|
+ */
|
|
|
const flattenCateIds = (items: (number | number[])[]): Set<number> => {
|
|
|
+ // 初始化一个空的Set集合,用于存储最终展平的类别ID
|
|
|
const result = new Set<number>()
|
|
|
|
|
|
+ /**
|
|
|
+ * 递归函数,用于展平类别ID
|
|
|
+ * 如果当前项是一个数组,则遍历数组的每一项并递归调用flatten
|
|
|
+ * 如果当前项是一个数字,则将其添加到result集合中
|
|
|
+ *
|
|
|
+ * @param item - 当前要处理的类别ID或类别ID数组
|
|
|
+ */
|
|
|
const flatten = (item: number | number[]) => {
|
|
|
+ // 判断当前项是否为数组,如果是,则递归处理数组中的每一项
|
|
|
if (isArray(item)) {
|
|
|
item.forEach(flatten)
|
|
|
} else {
|
|
|
+ // 如果当前项不是数组,则将其添加到result集合中
|
|
|
result.add(item)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 遍历输入的items数组,对每一项调用flatten函数进行展平处理
|
|
|
items.forEach(flatten)
|
|
|
|
|
|
+ // 返回包含所有展平后类别ID的Set集合
|
|
|
return result
|
|
|
}
|
|
|
+/**
|
|
|
+ * 保存表单数据
|
|
|
+ * 此函数在用户提交表单时被触发,它首先验证表单数据的有效性,如果有效则处理数据并调用相应的API进行保存
|
|
|
+ * @param {FormInstance | undefined} formEl - 表单实例,用于表单验证和获取表单数据
|
|
|
+ * @returns {Promise<Array>} - 返回一个空数组,当前函数没有实际返回值,但为了保持接口一致性,返回了空数组
|
|
|
+ */
|
|
|
const save = async (formEl: FormInstance | undefined) => {
|
|
|
+ // 检查表单实例是否存在,如果不存在则返回空数组
|
|
|
if (!formEl) return []
|
|
|
+
|
|
|
+ // 验证表单数据,如果验证失败,则显示错误消息并中断保存流程
|
|
|
const valid = await formEl.validate((valid, fields) => {
|
|
|
if (valid) {
|
|
|
console.log('submit!')
|
|
|
} else {
|
|
|
for (const key in fields) {
|
|
|
- // console.log(fields)
|
|
|
ElMessage.error(fields[key][0].message)
|
|
|
return
|
|
|
}
|
|
|
console.log('error submit!', fields)
|
|
|
}
|
|
|
})
|
|
|
- // console.log(valid, 'params')
|
|
|
+
|
|
|
+ // 如果表单数据有效,则准备数据并调用API进行保存
|
|
|
if (valid) {
|
|
|
+ // 将分类ID转换为数组形式,以便后续处理
|
|
|
const cateIds: number[] = Array.from(flattenCateIds(formData.cate_ids)).map((re) => re)
|
|
|
console.log(cateIds, 'cateIds')
|
|
|
+
|
|
|
+ // 构建要保存的商品数据对象
|
|
|
const data: goodsData = {
|
|
|
store_id: formData.store_id,
|
|
|
name: formData.name,
|
|
|
@@ -184,7 +259,7 @@ const save = async (formEl: FormInstance | undefined) => {
|
|
|
slider_image: formData.slider_image,
|
|
|
info: formData.info,
|
|
|
image: formData.image,
|
|
|
- keyword: formData.keyword,
|
|
|
+ keyword: formData.keyword || [],
|
|
|
cate_ids: cateIds,
|
|
|
postage: formData.postage,
|
|
|
temp_id: formData.temp_id,
|
|
|
@@ -198,18 +273,28 @@ const save = async (formEl: FormInstance | undefined) => {
|
|
|
sort: formData.sort,
|
|
|
is_show: formData.is_show
|
|
|
}
|
|
|
+
|
|
|
+ // 根据商品规格类型调整attrs_value字段
|
|
|
if (formData.spec_type == 0) {
|
|
|
data.attrs_value = [{ ...attrs_value }]
|
|
|
}
|
|
|
+ // 多规格商品处理
|
|
|
+ if (formData.spec_type == 1) {
|
|
|
+ data.attrs_value = productAttrList.value.value.map((res) => {
|
|
|
+ return res
|
|
|
+ })
|
|
|
+ }
|
|
|
try {
|
|
|
let res = {}
|
|
|
- console.log(params.type == 'add', '11111')
|
|
|
+ // 根据操作类型(添加或编辑)调用相应的API
|
|
|
if (params.type == 'add') {
|
|
|
res = await addProduct(data)
|
|
|
} else if (params.type == 'edit') {
|
|
|
data.id = formData.id
|
|
|
res = await putProduct(data)
|
|
|
}
|
|
|
+
|
|
|
+ // 处理API调用结果,如果成功则显示成功消息并询问用户是否返回商品列表
|
|
|
if (res) {
|
|
|
let title = '添加'
|
|
|
if (params.type == 'edit') {
|
|
|
@@ -231,9 +316,10 @@ const save = async (formEl: FormInstance | undefined) => {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
const loadingData = ref(false)
|
|
|
onMounted(async () => {
|
|
|
+ //加载商品规格
|
|
|
+ ruleList()
|
|
|
if (params.type == 'add') {
|
|
|
pageTitle.value = '添加商品'
|
|
|
} else if (params.type == 'edit') {
|
|
|
@@ -248,7 +334,7 @@ onMounted(async () => {
|
|
|
formData.video_link = data.video_link
|
|
|
formData.slider_image = data.slider_image
|
|
|
formData.image = data.image
|
|
|
- formData.keyword = data.keyword.split(',')
|
|
|
+ formData.keyword = data.keyword ? data.keyword.split(',') : []
|
|
|
formData.cate_ids = data.cate_ids.map((re) => parseInt(re))
|
|
|
formData.postage = data.postage
|
|
|
formData.temp_id = data.temp_id
|
|
|
@@ -272,6 +358,9 @@ onMounted(async () => {
|
|
|
attrs_value.suk = values.suk
|
|
|
attrs_value.image = [values.image]
|
|
|
}
|
|
|
+ // if(formData.spec_type == 1){
|
|
|
+
|
|
|
+ // }
|
|
|
getStore('', data.store_id)
|
|
|
} catch (error) {
|
|
|
console.log(error, 'error')
|
|
|
@@ -329,6 +418,219 @@ const getStore = async (query = '', id = '') => {
|
|
|
storeLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
+const selectRuleList = ref<productRule[]>([]) //可选规格列表
|
|
|
+const selectRuleIndex = ref() //选中的规格下标
|
|
|
+const actionAttrs = reactive<productRuleValue[]>([]) //选中的规格
|
|
|
+const addAttrShow = ref(false) //添加商品规格显示
|
|
|
+//新增规格输入框
|
|
|
+const attrItem = reactive({
|
|
|
+ name: '',
|
|
|
+ value: ''
|
|
|
+})
|
|
|
+const attrBase = reactive<AttrBaseItem[]>([
|
|
|
+ {
|
|
|
+ bar_code: '', //产品编号
|
|
|
+ cost: 0, //成本价
|
|
|
+ detail: {},
|
|
|
+ integral: 0, //积分
|
|
|
+ ot_price: 0, //原价
|
|
|
+ pic: [], //图片
|
|
|
+ price: 0, //售价
|
|
|
+ stock: 0, //库存
|
|
|
+ weight: 0, //重量
|
|
|
+ volume: 0 //体积
|
|
|
+ }
|
|
|
+])
|
|
|
+//商品多规格数据保存
|
|
|
+const productAttrList = ref<{
|
|
|
+ attr: any[]
|
|
|
+ header: any[]
|
|
|
+ value: any[]
|
|
|
+}>({
|
|
|
+ attr: [],
|
|
|
+ header: [],
|
|
|
+ value: []
|
|
|
+})
|
|
|
+/**
|
|
|
+ * 加载商品规格列表
|
|
|
+ */
|
|
|
+const ruleList = async () => {
|
|
|
+ const res = await getProductRule({ page: 1, limit: 100 })
|
|
|
+ selectRuleList.value = res.data
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 选择规则
|
|
|
+ *
|
|
|
+ * @param ruleIndex 规则在列表中的索引,用于标识特定的规则
|
|
|
+ */
|
|
|
+const selectAttrs = (ruleIndex: number) => {
|
|
|
+ actionAttrs.splice(0, actionAttrs.length - 1)
|
|
|
+ // 将选中的规则属性赋值给操作属性对象,以便在界面上进行展示或进一步操作
|
|
|
+ actionAttrs.push(...selectRuleList.value[ruleIndex].rule_value)
|
|
|
+ // 打印操作属性对象到控制台,用于调试目的
|
|
|
+ // console.log(actionAttrs, 'actionItem')
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 删除规则
|
|
|
+ *
|
|
|
+ * @param tag 规则对象
|
|
|
+ */
|
|
|
+const delAttrlist = (ind: number) => {
|
|
|
+ actionAttrs.splice(ind, 1)
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 删除规则属性
|
|
|
+ *
|
|
|
+ * @param name 规则属性名称
|
|
|
+ * @param tag 规则属性对象
|
|
|
+ */
|
|
|
+const delAttrlistItem = (name: string, tag: productRuleValue) => {
|
|
|
+ tag.detail.splice(tag.detail.indexOf(name), 1)
|
|
|
+}
|
|
|
+/**
|
|
|
+ * todo: 添加规则属性
|
|
|
+ * @param tag 要添加的规格参数值
|
|
|
+ */
|
|
|
+const addAttrItem = (tag: productRuleValue) => {
|
|
|
+ if (tag.detail.indexOf(tag.detailValue) > -1) {
|
|
|
+ ElMessage.error('规格参数已存在')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (tag.detailValue) {
|
|
|
+ tag.detail.push(tag.detailValue)
|
|
|
+ }
|
|
|
+ tag.attrHidden = ''
|
|
|
+ tag.detailValue = ''
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 生成多规格填写列表
|
|
|
+ */
|
|
|
+const attrsConfirm = async () => {
|
|
|
+ const { data } = await getAttrSelectList(formData.id || 0, actionAttrs)
|
|
|
+ const attrs = {
|
|
|
+ attr: data.attr,
|
|
|
+ header: data.header,
|
|
|
+ value: data.value.map((re) => {
|
|
|
+ re.pic = re.pic ? [re.pic] : []
|
|
|
+ return re
|
|
|
+ })
|
|
|
+ }
|
|
|
+ productAttrList.value = attrs
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 添加规则
|
|
|
+ */
|
|
|
+const addAttr = () => {
|
|
|
+ actionAttrs.push({
|
|
|
+ attrHidden: '',
|
|
|
+ detail: [attrItem.value],
|
|
|
+ detailValue: '',
|
|
|
+ value: attrItem.name
|
|
|
+ })
|
|
|
+ attrItem.value = ''
|
|
|
+ attrItem.name = ''
|
|
|
+ addAttrShow.value = false
|
|
|
+}
|
|
|
+/**
|
|
|
+ *
|
|
|
+ * @param index 删除的规格属性下标
|
|
|
+ */
|
|
|
+const delAttrItem = (index: number, arr: any[]) => {
|
|
|
+ arr.splice(index, 1)
|
|
|
+ console.log(arr, 'index', index)
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 初始化批量设置规格
|
|
|
+ */
|
|
|
+const initAttrBase = () => {
|
|
|
+ attrBase[0] = {
|
|
|
+ bar_code: '', //产品编号
|
|
|
+ cost: 0, //成本价
|
|
|
+ detail: {},
|
|
|
+ integral: 0, //积分
|
|
|
+ ot_price: 0, //原价
|
|
|
+ pic: [], //图片
|
|
|
+ price: 0, //售价
|
|
|
+ stock: 0, //库存
|
|
|
+ weight: 0, //重量
|
|
|
+ volume: 0 //体积
|
|
|
+ }
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 批量设置
|
|
|
+ */
|
|
|
+const batchSet = () => {
|
|
|
+ const value = productAttrList.value.value
|
|
|
+ for (let i = 0; i < value.length; i++) {
|
|
|
+ value[i].pic = attrBase[0].pic
|
|
|
+ value[i].bar_code = attrBase[0].bar_code
|
|
|
+ value[i].cost = attrBase[0].cost
|
|
|
+ value[i].integral = attrBase[0].integral
|
|
|
+ value[i].price = attrBase[0].price
|
|
|
+ value[i].ot_price = attrBase[0].ot_price
|
|
|
+ value[i].stock = attrBase[0].stock
|
|
|
+ value[i].weight = attrBase[0].weight
|
|
|
+ value[i].volume = attrBase[0].volume
|
|
|
+ }
|
|
|
+}
|
|
|
+const upImageButtomRef = useTemplateRef('upImageButtomRef')
|
|
|
+
|
|
|
+const selectPic = ref<string[]>([])
|
|
|
+const selectPicIndex = ref(0)
|
|
|
+const selectPicObj = ref('header')
|
|
|
+
|
|
|
+/**
|
|
|
+ * 点击上传图片回调函数
|
|
|
+ * @param {string[]} pic - 图片数组
|
|
|
+ * @param {number} index - 图片在数组中的索引
|
|
|
+ * @param {string} type - 图片类型
|
|
|
+ */
|
|
|
+const clickUpImage = (index: number, type: string) => {
|
|
|
+ // 设置当前选中图片的类型
|
|
|
+ selectPicObj.value = type
|
|
|
+ // 设置当前选中图片的索引
|
|
|
+ selectPicIndex.value = index
|
|
|
+ // 获取上传图片元素
|
|
|
+ const picElment = unref(upImageButtomRef)
|
|
|
+ // 设置当前操作的图片数组
|
|
|
+ selectPic.value = []
|
|
|
+ // 日志输出上传图片元素,用于调试
|
|
|
+ // console.log(picElment, 'picElment')
|
|
|
+ // 调用编辑图片方法,这里以添加图片为例
|
|
|
+ picElment?.editImage(0, 'add')
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 根据不同的图片选择对象类型,更新相应的图片数组
|
|
|
+ * @param {string[]} pic - 要更新的图片数组
|
|
|
+ */
|
|
|
+const changePic = (pic: string[]) => {
|
|
|
+ console.log(pic, 'pic')
|
|
|
+ // 根据选中的图片对象类型,更新对应的图片数组
|
|
|
+ switch (selectPicObj.value) {
|
|
|
+ case 'header':
|
|
|
+ // 更新产品属性列表中的图片数组
|
|
|
+ productAttrList.value.value[selectPicIndex.value].pic = pic
|
|
|
+ break
|
|
|
+ case 'attrBase':
|
|
|
+ // 更新基础属性中的图片数组
|
|
|
+ attrBase[0].pic = pic
|
|
|
+ break
|
|
|
+ case 'banner':
|
|
|
+ // 更新表单数据中的图片数组
|
|
|
+ formData.image = pic
|
|
|
+ break
|
|
|
+ case 'attrOne':
|
|
|
+ // 更新单规格的图片数组
|
|
|
+ attrs_value.image = pic
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ // 其他情况不做处理
|
|
|
+ break
|
|
|
+ }
|
|
|
+}
|
|
|
</script>
|
|
|
<template>
|
|
|
<ContentWrap
|
|
|
@@ -395,7 +697,11 @@ const getStore = async (query = '', id = '') => {
|
|
|
/>
|
|
|
</ElFormItem>
|
|
|
<ElFormItem label="商品封面图" prop="image">
|
|
|
- <UpImgButtom v-model="formData.image" />
|
|
|
+ <TableImage
|
|
|
+ :isShowImageViewer="false"
|
|
|
+ :src="formData.image[0]"
|
|
|
+ @click="clickUpImage(0, 'banner')"
|
|
|
+ />
|
|
|
</ElFormItem>
|
|
|
<ElFormItem label="商品轮播图" prop="slider_image">
|
|
|
<UpImgButtom
|
|
|
@@ -422,9 +728,19 @@ const getStore = async (query = '', id = '') => {
|
|
|
</ElFormItem>
|
|
|
</ElTabPane>
|
|
|
<ElTabPane label="商品规格" :name="2">
|
|
|
+ <ElFormItem label="规格类型" prop="spec_type">
|
|
|
+ <ElRadioGroup v-model="formData.spec_type">
|
|
|
+ <ElRadioButton label="单规格" :value="0" />
|
|
|
+ <ElRadioButton label="多规格" :value="1" />
|
|
|
+ </ElRadioGroup>
|
|
|
+ </ElFormItem>
|
|
|
<template v-if="formData.spec_type == 0">
|
|
|
<ElFormItem label="商品规格图" prop="sutimage">
|
|
|
- <UpImgButtom v-model="attrs_value.image" />
|
|
|
+ <TableImage
|
|
|
+ :isShowImageViewer="false"
|
|
|
+ :src="attrs_value.image[0]"
|
|
|
+ @click="clickUpImage(0, 'attrOne')"
|
|
|
+ />
|
|
|
</ElFormItem>
|
|
|
<ElFormItem label="售价" prop="price">
|
|
|
<ElInput type="number" v-model="attrs_value.price" placeholder="请输入商品单位" />
|
|
|
@@ -439,18 +755,210 @@ const getStore = async (query = '', id = '') => {
|
|
|
<ElInput type="number" v-model="attrs_value.stock" placeholder="请输入商品单位" />
|
|
|
</ElFormItem>
|
|
|
</template>
|
|
|
+ <template v-if="formData.spec_type == 1">
|
|
|
+ <ElFormItem label="选择规格" prop="spec_type">
|
|
|
+ <div class="w-full">
|
|
|
+ <ElSelect
|
|
|
+ v-model="selectRuleIndex"
|
|
|
+ placeholder="选择需要生成的规格"
|
|
|
+ class="w-240px! mr-10px"
|
|
|
+ >
|
|
|
+ <ElOption
|
|
|
+ v-for="(item, ind) in selectRuleList"
|
|
|
+ :key="ind"
|
|
|
+ :label="item.rule_name"
|
|
|
+ :value="ind"
|
|
|
+ />
|
|
|
+ </ElSelect>
|
|
|
+ <BaseButton @click="selectAttrs(selectRuleIndex)">确定</BaseButton>
|
|
|
+ <BaseButton>添加新规格</BaseButton>
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- <ElFormItem label="规格类型" prop="spec_type">
|
|
|
- <el-radio-group v-model="formData.spec_type">
|
|
|
- <el-radio-button label="单规格" :value="0" />
|
|
|
- <el-radio-button label="多规格" :value="1" />
|
|
|
- </el-radio-group>
|
|
|
- </ElFormItem> -->
|
|
|
-
|
|
|
- <!-- <ElFormItem v-if="formData.spec_type == 1" label="" prop="spec_type">
|
|
|
- <BaseButton>添加新规格</BaseButton>
|
|
|
- <BaseButton>立即生成</BaseButton>
|
|
|
- </ElFormItem> -->
|
|
|
+ <template v-if="actionAttrs.length > 0">
|
|
|
+ <div class="mt-10px w-full" v-for="(item, index) in actionAttrs" :key="index">
|
|
|
+ <div class="w-100 mb-10px!">
|
|
|
+ <ElTag
|
|
|
+ type="info"
|
|
|
+ effect="plain"
|
|
|
+ class="mr-10px!"
|
|
|
+ closable
|
|
|
+ @close="delAttrlist(index)"
|
|
|
+ >
|
|
|
+ {{ item.value }}
|
|
|
+ </ElTag>
|
|
|
+ </div>
|
|
|
+ <ElTag
|
|
|
+ class="mr-10px! mb-10px!"
|
|
|
+ v-for="(name, ind) in item.detail"
|
|
|
+ :key="ind"
|
|
|
+ closable
|
|
|
+ @close="delAttrlistItem(name, item)"
|
|
|
+ >
|
|
|
+ {{ name }}
|
|
|
+ </ElTag>
|
|
|
+ <ElInput
|
|
|
+ v-model="item.detailValue"
|
|
|
+ class="w-20! mr-10px! mb-10px!"
|
|
|
+ size="small"
|
|
|
+ @keyup.enter="addAttrItem(item)"
|
|
|
+ />
|
|
|
+ <BaseButton
|
|
|
+ type="primary"
|
|
|
+ class="mb-10px!"
|
|
|
+ size="small"
|
|
|
+ @click="addAttrItem(item)"
|
|
|
+ >
|
|
|
+ 添加
|
|
|
+ </BaseButton>
|
|
|
+ </div>
|
|
|
+ <BaseButton type="primary" class="mb-10px!" @click="attrsConfirm">
|
|
|
+ 立即生成
|
|
|
+ </BaseButton>
|
|
|
+ <BaseButton type="primary" class="mb-10px!" @click="addAttrShow = true">
|
|
|
+ 添加新规格
|
|
|
+ </BaseButton>
|
|
|
+ </template>
|
|
|
+ </ElFormItem>
|
|
|
+ <template v-if="addAttrShow">
|
|
|
+ <ElFormItem label="规格">
|
|
|
+ <ElInput v-model="attrItem.name" placeholder="请输入规格名称" />
|
|
|
+ </ElFormItem>
|
|
|
+ <ElFormItem label="规格值">
|
|
|
+ <ElInput v-model="attrItem.value" placeholder="请输入规格参数" />
|
|
|
+ </ElFormItem>
|
|
|
+ <div class="w-full flex flex-justify-center">
|
|
|
+ <BaseButton type="primary" class="mb-10px!" @click="addAttr"> 确定 </BaseButton>
|
|
|
+ <BaseButton class="mb-10px!" @click="addAttrShow = false"> 取消 </BaseButton>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template v-if="productAttrList.header.length > 0">
|
|
|
+ <ElFormItem label="批量设置">
|
|
|
+ <ElTable
|
|
|
+ header-cell-class-name="bg-gray-100!"
|
|
|
+ :data="attrBase"
|
|
|
+ class="w-100%"
|
|
|
+ :border="true"
|
|
|
+ stripe
|
|
|
+ >
|
|
|
+ <ElTableColumn prop="pic" label="图片" width="100px">
|
|
|
+ <template #header> <span>图片</span><span class="text-red">*</span> </template>
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <TableImage
|
|
|
+ :isShowImageViewer="false"
|
|
|
+ :src="row.pic[0]"
|
|
|
+ @click="clickUpImage($index, 'attrBase')"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn prop="price" label="售价">
|
|
|
+ <template #header> <span>售价</span><span class="text-red">*</span> </template>
|
|
|
+ <template #default="{ row }">
|
|
|
+ <ElInput type="number" v-model="row.price" />
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <!-- <ElTableColumn prop="integral" label="积分">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <ElInput type="number" v-model="row.integral" />
|
|
|
+ </template>
|
|
|
+ </ElTableColumn> -->
|
|
|
+ <ElTableColumn prop="cost" label="成本价">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <ElInput type="number" v-model="row.cost" />
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn prop="ot_price" label="原价">
|
|
|
+ <template #header> <span>原价</span><span class="text-red">*</span> </template>
|
|
|
+ <template #default="{ row }">
|
|
|
+ <ElInput type="number" v-model="row.ot_price" />
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn prop="stock" label="库存">
|
|
|
+ <template #header> <span>库存</span><span class="text-red">*</span> </template>
|
|
|
+ <template #default="{ row }">
|
|
|
+ <ElInput type="number" v-model="row.stock" />
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn prop="bar_code" label="产品编号">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <ElInput v-model="row.bar_code" />
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn prop="weight" label="重量">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <ElInput v-model="row.weight" />
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn prop="volume" label="体积">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <ElInput v-model="row.volume" />
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn fixed="right" label="操作" width="140px">
|
|
|
+ <BaseButton link type="primary" @click="batchSet"> 批量设置 </BaseButton>
|
|
|
+ <ElDivider direction="vertical" />
|
|
|
+ <BaseButton @click="initAttrBase" link type="primary"> 清空 </BaseButton>
|
|
|
+ </ElTableColumn>
|
|
|
+ </ElTable>
|
|
|
+ </ElFormItem>
|
|
|
+ <ElFormItem label="商品属性">
|
|
|
+ <ElTable
|
|
|
+ header-cell-class-name="bg-gray-100!"
|
|
|
+ :data="productAttrList.value"
|
|
|
+ class="w-100%"
|
|
|
+ :border="true"
|
|
|
+ stripe
|
|
|
+ >
|
|
|
+ <template v-for="(item, index) in productAttrList.header" :key="index">
|
|
|
+ <ElTableColumn
|
|
|
+ v-if="item.slot !== 'action'"
|
|
|
+ :label="item.title"
|
|
|
+ :minWidth="item.minWidth"
|
|
|
+ >
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <span v-if="item.key">
|
|
|
+ {{ row[item.key] }}
|
|
|
+ </span>
|
|
|
+ <template v-else-if="item.slot">
|
|
|
+ <TableImage
|
|
|
+ :isShowImageViewer="false"
|
|
|
+ :src="row.pic[0]"
|
|
|
+ v-if="item.slot == 'pic'"
|
|
|
+ @click="clickUpImage($index, 'header')"
|
|
|
+ />
|
|
|
+ <template v-else-if="item.slot == 'action'">
|
|
|
+ <BaseButton
|
|
|
+ @click="delAttrItem($index, productAttrList.value)"
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </BaseButton>
|
|
|
+ </template>
|
|
|
+ <ElInput v-else v-model="row[item.slot]" />
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ fixed="right"
|
|
|
+ v-else
|
|
|
+ :label="item.title"
|
|
|
+ :minWidth="item.minWidth"
|
|
|
+ >
|
|
|
+ <template #default="{ $index }">
|
|
|
+ <BaseButton
|
|
|
+ @click="delAttrItem($index, productAttrList.value)"
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </BaseButton>
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ </template>
|
|
|
+ </ElTable>
|
|
|
+ </ElFormItem>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
</ElTabPane>
|
|
|
<ElTabPane label="商品详情" :name="3">
|
|
|
<Editor v-model="formData.description" ref="editorRef" />
|
|
|
@@ -462,6 +970,12 @@ const getStore = async (query = '', id = '') => {
|
|
|
</ElTabPane>
|
|
|
</ElTabs>
|
|
|
</ElForm>
|
|
|
+ <UpImgButtom
|
|
|
+ class="pos-absolute left-[-200px]"
|
|
|
+ ref="upImageButtomRef"
|
|
|
+ v-model="selectPic"
|
|
|
+ @change="changePic"
|
|
|
+ />
|
|
|
</ContentWrap>
|
|
|
<div
|
|
|
class="text-center border-t-1px border-solid border-gray-200 py-10px mt-10px position-sticky bottom-0 left-0 right-0 bg-white"
|