Merge remote-tracking branch 'yudao/dev' into dev

This commit is contained in:
dhb52
2023-05-29 18:23:18 +08:00
49 changed files with 3272 additions and 340 deletions

View File

@@ -2,42 +2,49 @@
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="68px"
>
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
clearable
placeholder="请输入名称"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
<el-button
v-hasPermi="['product:property:create']"
plain
type="primary"
@click="openForm('create')"
v-hasPermi="['product:property:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
<Icon class="mr-5px" icon="ep:plus" />
新增
</el-button>
</el-form-item>
</el-form>
@@ -46,23 +53,23 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="名称" align="center" />
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column align="center" label="编号" prop="id" />
<el-table-column align="center" label="名称" prop="name" />
<el-table-column :show-overflow-tooltip="true" align="center" label="备注" prop="remark" />
<el-table-column
label="创建时间"
:formatter="dateFormatter"
align="center"
label="创建时间"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<el-table-column align="center" label="操作">
<template #default="scope">
<el-button
v-hasPermi="['product:property:update']"
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['product:property:update']"
>
编辑
</el-button>
@@ -70,10 +77,10 @@
<router-link :to="'/property/value/' + scope.row.id">属性值</router-link>
</el-button>
<el-button
v-hasPermi="['product:property:delete']"
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['product:property:delete']"
>
删除
</el-button>
@@ -82,9 +89,9 @@
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
@@ -92,10 +99,11 @@
<!-- 表单弹窗添加/修改 -->
<PropertyForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="ProductProperty">
<script lang="ts" name="ProductProperty" setup>
import { dateFormatter } from '@/utils/formatTime'
import * as PropertyApi from '@/api/mall/product/property'
import PropertyForm from './PropertyForm.vue'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化

View File

@@ -0,0 +1,178 @@
<template>
<ContentWrap v-loading="formLoading">
<el-tabs v-model="activeName">
<el-tab-pane label="商品信息" name="basicInfo">
<BasicInfoForm
ref="basicInfoRef"
v-model:activeName="activeName"
:propFormData="formData"
/>
</el-tab-pane>
<el-tab-pane label="商品详情" name="description">
<DescriptionForm
ref="descriptionRef"
v-model:activeName="activeName"
:propFormData="formData"
/>
</el-tab-pane>
<el-tab-pane label="其他设置" name="otherSettings">
<OtherSettingsForm
ref="otherSettingsRef"
v-model:activeName="activeName"
:propFormData="formData"
/>
</el-tab-pane>
</el-tabs>
<el-form>
<el-form-item style="float: right">
<el-button :loading="formLoading" type="primary" @click="submitForm">保存</el-button>
<el-button @click="close">返回</el-button>
</el-form-item>
</el-form>
</ContentWrap>
</template>
<script lang="ts" name="ProductSpuForm" setup>
import { cloneDeep } from 'lodash-es'
import { useTagsViewStore } from '@/store/modules/tagsView'
import { BasicInfoForm, DescriptionForm, OtherSettingsForm } from './components'
// 业务api
import * as ProductSpuApi from '@/api/mall/product/spu'
import { convertToInteger, formatToFraction } from '@/utils'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const { push, currentRoute } = useRouter() // 路由
const { params } = useRoute() // 查询参数
const { delView } = useTagsViewStore() // 视图操作
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const activeName = ref('basicInfo') // Tag 激活的窗口
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() // 商品信息Ref
const descriptionRef = ref<ComponentRef<typeof DescriptionForm>>() // 商品详情Ref
const otherSettingsRef = ref<ComponentRef<typeof OtherSettingsForm>>() // 其他设置Ref
// spu 表单数据
const formData = ref<ProductSpuApi.SpuType>({
name: '', // 商品名称
categoryId: null, // 商品分类
keyword: '', // 关键字
unit: null, // 单位
picUrl: '', // 商品封面图
sliderPicUrls: [], // 商品轮播图
introduction: '', // 商品简介
deliveryTemplateId: 1, // 运费模版
brandId: null, // 商品品牌
specType: false, // 商品规格
subCommissionType: false, // 分销类型
skus: [
{
price: 0, // 商品价格
marketPrice: 0, // 市场价
costPrice: 0, // 成本价
barCode: '', // 商品条码
picUrl: '', // 图片地址
stock: 0, // 库存
weight: 0, // 商品重量
volume: 0, // 商品体积
subCommissionFirstPrice: 0, // 一级分销的佣金
subCommissionSecondPrice: 0 // 二级分销的佣金
}
],
description: '', // 商品详情
sort: 0, // 商品排序
giveIntegral: 0, // 赠送积分
virtualSalesCount: 0, // 虚拟销量
recommendHot: false, // 是否热卖
recommendBenefit: false, // 是否优惠
recommendBest: false, // 是否精品
recommendNew: false, // 是否新品
recommendGood: false // 是否优品
})
/** 获得详情 */
const getDetail = async () => {
const id = params.spuId as number
if (id) {
formLoading.value = true
try {
const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.SpuType
res.skus.forEach((item) => {
// 回显价格分转元
item.price = formatToFraction(item.price)
item.marketPrice = formatToFraction(item.marketPrice)
item.costPrice = formatToFraction(item.costPrice)
item.subCommissionFirstPrice = formatToFraction(item.subCommissionFirstPrice)
item.subCommissionSecondPrice = formatToFraction(item.subCommissionSecondPrice)
})
formData.value = res
} finally {
formLoading.value = false
}
}
}
/** 提交按钮 */
const submitForm = async () => {
// 提交请求
formLoading.value = true
// 三个表单逐一校验,如果有一个表单校验不通过则切换到对应表单,如果有两个及以上的情况则切换到最前面的一个并弹出提示消息
// 校验各表单
try {
await unref(basicInfoRef)?.validate()
await unref(descriptionRef)?.validate()
await unref(otherSettingsRef)?.validate()
const deepCopyFormData = cloneDeep(unref(formData.value)) // 深拷贝一份 fix:这样最终 server 端不满足,不需要恢复,
// TODO 兜底处理 sku 空数据
formData.value.skus.forEach((sku) => {
// 因为是空数据这里判断一下商品条码是否为空就行
if (sku.barCode === '') {
const index = deepCopyFormData.skus.findIndex(
(item) => JSON.stringify(item.properties) === JSON.stringify(sku.properties)
)
// 删除这条 sku
deepCopyFormData.skus.splice(index, 1)
}
})
deepCopyFormData.skus.forEach((item) => {
// 给sku name赋值
item.name = deepCopyFormData.name
// sku相关价格元转分
item.price = convertToInteger(item.price)
item.marketPrice = convertToInteger(item.marketPrice)
item.costPrice = convertToInteger(item.costPrice)
item.subCommissionFirstPrice = convertToInteger(item.subCommissionFirstPrice)
item.subCommissionSecondPrice = convertToInteger(item.subCommissionSecondPrice)
})
// 处理轮播图列表
const newSliderPicUrls = []
deepCopyFormData.sliderPicUrls.forEach((item) => {
// 如果是前端选的图
typeof item === 'object' ? newSliderPicUrls.push(item.url) : newSliderPicUrls.push(item)
})
deepCopyFormData.sliderPicUrls = newSliderPicUrls
// 校验都通过后提交表单
const data = deepCopyFormData as ProductSpuApi.SpuType
const id = params.spuId as number
if (!id) {
await ProductSpuApi.createSpu(data)
message.success(t('common.createSuccess'))
} else {
await ProductSpuApi.updateSpu(data)
message.success(t('common.updateSuccess'))
}
close()
} finally {
formLoading.value = false
}
}
/** 关闭按钮 */
const close = () => {
delView(unref(currentRoute))
push('/product/product-spu')
}
/** 初始化 */
onMounted(async () => {
await getDetail()
})
</script>

View File

@@ -0,0 +1,274 @@
<template>
<el-form ref="productSpuBasicInfoRef" :model="formData" :rules="rules" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="商品名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入商品名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<!-- TODO @puhui999只能选根节点 -->
<el-form-item label="商品分类" prop="categoryId">
<el-tree-select
v-model="formData.categoryId"
:data="categoryList"
:props="defaultProps"
check-strictly
class="w-1/1"
node-key="id"
placeholder="请选择商品分类"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="商品关键字" prop="keyword">
<el-input v-model="formData.keyword" placeholder="请输入商品关键字" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单位" prop="unit">
<el-select v-model="formData.unit" class="w-1/1" placeholder="请选择单位">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.PRODUCT_UNIT)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="商品简介" prop="introduction">
<el-input
v-model="formData.introduction"
:rows="3"
placeholder="请输入商品简介"
type="textarea"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="商品封面图" prop="picUrl">
<UploadImg v-model="formData.picUrl" height="80px" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="商品轮播图" prop="sliderPicUrls">
<UploadImgs v-model:modelValue="formData.sliderPicUrls" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="运费模板" prop="deliveryTemplateId">
<el-select v-model="formData.deliveryTemplateId" placeholder="请选择">
<el-option v-for="item in []" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-button class="ml-20px">运费模板</el-button>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="品牌" prop="brandId">
<el-select v-model="formData.brandId" placeholder="请选择">
<el-option
v-for="item in brandList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="商品规格" props="specType">
<el-radio-group v-model="formData.specType" @change="onChangeSpec">
<el-radio :label="false" class="radio">单规格</el-radio>
<el-radio :label="true">多规格</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分销类型" props="subCommissionType">
<el-radio-group v-model="formData.subCommissionType" @change="changeSubCommissionType">
<el-radio :label="false">默认设置</el-radio>
<el-radio :label="true" class="radio">自行设置</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<!-- 多规格添加-->
<el-col :span="24">
<el-form-item v-if="formData.specType" label="商品属性">
<el-button class="mr-15px mb-10px" @click="attributesAddFormRef.open">添加规格</el-button>
<ProductAttributes :propertyList="propertyList" @success="generateSkus" />
</el-form-item>
<template v-if="formData.specType && propertyList.length > 0">
<el-form-item label="批量设置">
<SkuList :is-batch="true" :prop-form-data="formData" :propertyList="propertyList" />
</el-form-item>
<el-form-item label="属性列表">
<SkuList ref="skuListRef" :prop-form-data="formData" :propertyList="propertyList" />
</el-form-item>
</template>
<el-form-item v-if="!formData.specType">
<SkuList :prop-form-data="formData" :propertyList="propertyList" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<ProductAttributesAddForm ref="attributesAddFormRef" :propertyList="propertyList" />
</template>
<script lang="ts" name="ProductSpuBasicInfoForm" setup>
import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import { defaultProps, handleTree } from '@/utils/tree'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import type { SpuType } from '@/api/mall/product/spu'
import { UploadImg, UploadImgs } from '@/components/UploadFile'
import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index'
import * as ProductCategoryApi from '@/api/mall/product/category'
import { getSimpleBrandList } from '@/api/mall/product/brand'
const message = useMessage() // 消息弹窗
const props = defineProps({
propFormData: {
type: Object as PropType<SpuType>,
default: () => {}
},
activeName: propTypes.string.def('')
})
const attributesAddFormRef = ref() // 添加商品属性表单
const productSpuBasicInfoRef = ref() // 表单 Ref
const propertyList = ref([]) // 商品属性列表
const skuListRef = ref() // 商品属性列表Ref
/** 调用 SkuList generateTableData 方法*/
const generateSkus = (propertyList) => {
skuListRef.value.generateTableData(propertyList)
}
const formData = reactive<SpuType>({
name: '', // 商品名称
categoryId: null, // 商品分类
keyword: '', // 关键字
unit: '', // 单位
picUrl: '', // 商品封面图
sliderPicUrls: [], // 商品轮播图
introduction: '', // 商品简介
deliveryTemplateId: 1, // 运费模版
brandId: null, // 商品品牌
specType: false, // 商品规格
subCommissionType: false, // 分销类型
skus: []
})
const rules = reactive({
name: [required],
categoryId: [required],
keyword: [required],
unit: [required],
introduction: [required],
picUrl: [required],
sliderPicUrls: [required],
// deliveryTemplateId: [required],
brandId: [required],
specType: [required],
subCommissionType: [required]
})
/**
* 将传进来的值赋值给 formData
*/
watch(
() => props.propFormData,
(data) => {
if (!data) {
return
}
copyValueToTarget(formData, data)
formData.sliderPicUrls = data['sliderPicUrls'].map((item) => ({
url: item
}))
// TODO @puhui999if return减少嵌套层级
// 只有是多规格才处理
if (formData.specType) {
// 直接拿返回的 skus 属性逆向生成出 propertyList
const properties = []
formData.skus.forEach((sku) => {
sku.properties.forEach(({ propertyId, propertyName, valueId, valueName }) => {
// 添加属性
if (!properties.some((item) => item.id === propertyId)) {
properties.push({ id: propertyId, name: propertyName, values: [] })
}
// 添加属性值
const index = properties.findIndex((item) => item.id === propertyId)
if (!properties[index].values.some((value) => value.id === valueId)) {
properties[index].values.push({ id: valueId, name: valueName })
}
})
})
propertyList.value = properties
}
},
{
immediate: true
}
)
/**
* 表单校验
*/
const emit = defineEmits(['update:activeName'])
const validate = async () => {
// 校验表单
if (!productSpuBasicInfoRef) return
return await unref(productSpuBasicInfoRef).validate((valid) => {
if (!valid) {
message.warning('商品信息未完善!!')
emit('update:activeName', 'basicInfo')
// 目的截断之后的校验
throw new Error('商品信息未完善!!')
} else {
// 校验通过更新数据
Object.assign(props.propFormData, formData)
}
})
}
defineExpose({ validate })
/** 分销类型 */
const changeSubCommissionType = () => {
// 默认为零,类型切换后也要重置为零
for (const item of formData.skus) {
item.subCommissionFirstPrice = 0
item.subCommissionSecondPrice = 0
}
}
/** 选择规格 */
const onChangeSpec = () => {
// 重置商品属性列表
propertyList.value = []
// 重置sku列表
formData.skus = [
{
price: 0,
marketPrice: 0,
costPrice: 0,
barCode: '',
picUrl: '',
stock: 0,
weight: 0,
volume: 0,
subCommissionFirstPrice: 0,
subCommissionSecondPrice: 0
}
]
}
const categoryList = ref([]) // 分类树
const brandList = ref([]) // 精简商品品牌列表
onMounted(async () => {
// 获得分类树
const data = await ProductCategoryApi.getCategoryList({})
categoryList.value = handleTree(data, 'id', 'parentId')
// 获取商品品牌列表
brandList.value = await getSimpleBrandList()
})
</script>

View File

@@ -0,0 +1,83 @@
<template>
<el-form ref="descriptionFormRef" :model="formData" :rules="rules" label-width="120px">
<!--富文本编辑器组件-->
<el-form-item label="商品详情" prop="description">
<Editor v-model:modelValue="formData.description" />
</el-form-item>
</el-form>
</template>
<script lang="ts" name="DescriptionForm" setup>
import type { SpuType } from '@/api/mall/product/spu'
import { Editor } from '@/components/Editor'
import { PropType } from 'vue'
import { propTypes } from '@/utils/propTypes'
import { copyValueToTarget } from '@/utils'
const message = useMessage() // 消息弹窗
const props = defineProps({
propFormData: {
type: Object as PropType<SpuType>,
default: () => {}
},
activeName: propTypes.string.def('')
})
const descriptionFormRef = ref() // 表单Ref
const formData = ref<SpuType>({
description: '' // 商品详情
})
// 表单规则
const rules = reactive({
description: [required]
})
/**
* 富文本编辑器如果输入过再清空会有残留,需再重置一次
*/
watch(
() => formData.value.description,
(newValue) => {
if ('<p><br></p>' === newValue) {
formData.value.description = ''
}
},
{
deep: true,
immediate: true
}
)
/**
* 将传进来的值赋值给formData
*/
watch(
() => props.propFormData,
(data) => {
if (!data) return
// fix三个表单组件监听赋值必须使用 copyValueToTarget 使用 formData.value = data 会监听非常多次
copyValueToTarget(formData.value, data)
},
{
// fix: 去掉深度监听只有对象引用发生改变的时候才执行,解决改一动多的问题
immediate: true
}
)
/**
* 表单校验
*/
const emit = defineEmits(['update:activeName'])
const validate = async () => {
// 校验表单
if (!descriptionFormRef) return
return await unref(descriptionFormRef).validate((valid) => {
if (!valid) {
message.warning('商品详情为完善!!')
emit('update:activeName', 'description')
// 目的截断之后的校验
throw new Error('商品详情为完善!!')
} else {
// 校验通过更新数据
Object.assign(props.propFormData, formData.value)
}
})
}
defineExpose({ validate })
</script>

View File

@@ -0,0 +1,145 @@
<template>
<el-form ref="otherSettingsFormRef" :model="formData" :rules="rules" label-width="120px">
<el-row>
<el-col :span="24">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="商品排序" prop="sort">
<el-input-number v-model="formData.sort" :min="0" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="赠送积分" prop="giveIntegral">
<el-input-number v-model="formData.giveIntegral" :min="0" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="虚拟销量" prop="virtualSalesCount">
<el-input-number
v-model="formData.virtualSalesCount"
:min="0"
placeholder="请输入虚拟销量"
/>
</el-form-item>
</el-col>
</el-row>
</el-col>
<el-col :span="24">
<el-form-item label="商品推荐">
<el-checkbox-group v-model="checkboxGroup" @change="onChangeGroup">
<el-checkbox v-for="(item, index) in recommendOptions" :key="index" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
<el-col :span="24">
<!-- TODO tag展示暂时不考虑排序 -->
<el-form-item label="活动优先级">
<el-tag>默认</el-tag>
<el-tag class="ml-2" type="success">秒杀</el-tag>
<el-tag class="ml-2" type="info">砍价</el-tag>
<el-tag class="ml-2" type="warning">拼团</el-tag>
</el-form-item>
</el-col>
<!-- TODO @puhui999等优惠劵 ok 在搞 -->
<el-col :span="24">
<el-form-item label="赠送优惠劵">
<el-button>选择优惠券</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script lang="ts" name="OtherSettingsForm" setup>
import type { SpuType } from '@/api/mall/product/spu'
import { PropType } from 'vue'
import { propTypes } from '@/utils/propTypes'
import { copyValueToTarget } from '@/utils'
const message = useMessage() // 消息弹窗
const props = defineProps({
propFormData: {
type: Object as PropType<SpuType>,
default: () => {}
},
activeName: propTypes.string.def('')
})
const otherSettingsFormRef = ref() // 表单Ref
// 表单数据
const formData = ref<SpuType>({
sort: 1, // 商品排序
giveIntegral: 1, // 赠送积分
virtualSalesCount: 1, // 虚拟销量
recommendHot: false, // 是否热卖
recommendBenefit: false, // 是否优惠
recommendBest: false, // 是否精品
recommendNew: false, // 是否新品
recommendGood: false // 是否优品
})
// 表单规则
const rules = reactive({
sort: [required],
giveIntegral: [required],
virtualSalesCount: [required]
})
const recommendOptions = [
{ name: '是否热卖', value: 'recommendHot' },
{ name: '是否优惠', value: 'recommendBenefit' },
{ name: '是否精品', value: 'recommendBest' },
{ name: '是否新品', value: 'recommendNew' },
{ name: '是否优品', value: 'recommendGood' }
] // 商品推荐选项
const checkboxGroup = ref<string[]>([]) // 选中的推荐选项
/** 选择商品后赋值 */
const onChangeGroup = () => {
recommendOptions.forEach(({ value }) => {
formData.value[value] = checkboxGroup.value.includes(value)
})
}
/**
* 将传进来的值赋值给formData
*/
watch(
() => props.propFormData,
(data) => {
if (!data) {
return
}
copyValueToTarget(formData.value, data)
recommendOptions.forEach(({ value }) => {
if (formData.value[value] && !checkboxGroup.value.includes(value)) {
checkboxGroup.value.push(value)
}
})
},
{
immediate: true
}
)
/**
* 表单校验
*/
const emit = defineEmits(['update:activeName'])
const validate = async () => {
// 校验表单
if (!otherSettingsFormRef) return
return await unref(otherSettingsFormRef).validate((valid) => {
if (!valid) {
message.warning('商品其他设置未完善!!')
emit('update:activeName', 'otherSettings')
// 目的截断之后的校验
throw new Error('商品其他设置未完善!!')
} else {
// 校验通过更新数据
Object.assign(props.propFormData, formData.value)
}
})
}
defineExpose({ validate })
</script>

View File

@@ -0,0 +1,117 @@
<template>
<el-col v-for="(item, index) in attributeList" :key="index">
<div>
<el-text class="mx-1">属性名</el-text>
<el-tag class="mx-1" closable type="success" @close="handleCloseProperty(index)"
>{{ item.name }}
</el-tag>
</div>
<div>
<el-text class="mx-1">属性值</el-text>
<el-tag
v-for="(value, valueIndex) in item.values"
:key="value.id"
class="mx-1"
closable
@close="handleCloseValue(index, valueIndex)"
>
{{ value.name }}
</el-tag>
<el-input
v-show="inputVisible(index)"
:id="`input${index}`"
:ref="setInputRef"
v-model="inputValue"
class="!w-20"
size="small"
@blur="handleInputConfirm(index, item.id)"
@keyup.enter="handleInputConfirm(index, item.id)"
/>
<el-button
v-show="!inputVisible(index)"
class="button-new-tag ml-1"
size="small"
@click="showInput(index)"
>
+ 添加
</el-button>
</div>
<el-divider class="my-10px" />
</el-col>
</template>
<script lang="ts" name="ProductAttributes" setup>
import { ElInput } from 'element-plus'
import * as PropertyApi from '@/api/mall/product/property'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const inputValue = ref('') // 输入框值
const attributeIndex = ref<number | null>(null) // 获取焦点时记录当前属性项的index
// 输入框显隐控制
const inputVisible = computed(() => (index) => {
if (attributeIndex.value === null) return false
if (attributeIndex.value === index) return true
})
const inputRef = ref([]) //标签输入框Ref
/** 解决 ref 在 v-for 中的获取问题*/
const setInputRef = (el) => {
if (el === null || typeof el === 'undefined') return
// 如果不存在id相同的元素才添加
if (!inputRef.value.some((item) => item.input?.attributes.id === el.input?.attributes.id)) {
inputRef.value.push(el)
}
}
const attributeList = ref([]) // 商品属性列表
const props = defineProps({
propertyList: {
type: Array,
default: () => {}
}
})
watch(
() => props.propertyList,
(data) => {
if (!data) return
attributeList.value = data
},
{
deep: true,
immediate: true
}
)
/** 删除属性值*/
const handleCloseValue = (index, valueIndex) => {
attributeList.value[index].values?.splice(valueIndex, 1)
}
/** 删除属性*/
const handleCloseProperty = (index) => {
attributeList.value?.splice(index, 1)
}
/** 显示输入框并获取焦点 */
const showInput = async (index) => {
attributeIndex.value = index
inputRef.value[index].focus()
}
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
/** 输入框失去焦点或点击回车时触发 */
const handleInputConfirm = async (index, propertyId) => {
if (inputValue.value) {
// 保存属性值
try {
const id = await PropertyApi.createPropertyValue({ propertyId, name: inputValue.value })
attributeList.value[index].values.push({ id, name: inputValue.value })
message.success(t('common.createSuccess'))
emit('success', attributeList.value)
} catch {
message.error('添加失败,请重试') // TODO 缺少国际化
}
}
attributeIndex.value = null
inputValue.value = ''
}
</script>

View File

@@ -0,0 +1,98 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="80px"
>
<el-form-item label="属性名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" name="ProductPropertyForm" setup>
import * as PropertyApi from '@/api/mall/product/property'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('添加商品属性') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formData = ref({
name: ''
})
const formRules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
const attributeList = ref([]) // 商品属性列表
const props = defineProps({
propertyList: {
type: Array,
default: () => {}
}
})
watch(
() => props.propertyList,
(data) => {
if (!data) return
attributeList.value = data
},
{
deep: true,
immediate: true
}
)
/** 打开弹窗 */
const open = async () => {
dialogVisible.value = true
resetForm()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formData.value as PropertyApi.PropertyVO
// 检查属性是否已存在,如果有则返回属性和其下属性值
const res = await PropertyApi.getPropertyListAndValue({ name: data.name })
if (res.length === 0) {
const propertyId = await PropertyApi.createProperty(data)
attributeList.value.push({ id: propertyId, ...formData.value, values: [] })
} else {
if (res[0].values === null) {
res[0].values = []
}
attributeList.value.push(res[0]) // 因为只用一个
}
message.success(t('common.createSuccess'))
dialogVisible.value = false
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
name: '',
remark: ''
}
formRef.value?.resetFields()
}
</script>

View File

@@ -0,0 +1,317 @@
<template>
<el-table
:data="isBatch ? skuList : formData.skus"
border
class="tabNumWidth"
max-height="500"
size="small"
>
<el-table-column align="center" fixed="left" label="图片" min-width="100">
<template #default="{ row }">
<UploadImg v-model="row.picUrl" height="80px" width="100%" />
</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 }">
<!-- TODO puhui999展示成蓝色有点区分度哈 -->
{{ row.properties[index]?.valueName }}
</template>
</el-table-column>
</template>
<el-table-column align="center" label="商品条码" min-width="168">
<template #default="{ row }">
<el-input 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%" />
</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%"
/>
</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%"
/>
</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%" />
</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%" />
</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%" />
</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.subCommissionFirstPrice"
:min="0"
:precision="2"
:step="0.1"
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.subCommissionSecondPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
/>
</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>
</el-table>
</template>
<script lang="ts" name="SkuList" setup>
import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import { UploadImg } from '@/components/UploadFile'
import type { Property, SkuType, SpuType } from '@/api/mall/product/spu'
const props = defineProps({
propFormData: {
type: Object as PropType<SpuType>,
default: () => {}
},
propertyList: {
type: Array,
default: () => []
},
isBatch: propTypes.bool.def(false) // 是否作为批量操作组件
})
const formData = ref<SpuType>() // 表单数据
const skuList = ref<SkuType[]>([
{
price: 0, // 商品价格
marketPrice: 0, // 市场价
costPrice: 0, // 成本价
barCode: '', // 商品条码
picUrl: '', // 图片地址
stock: 0, // 库存
weight: 0, // 商品重量
volume: 0, // 商品体积
subCommissionFirstPrice: 0, // 一级分销的佣金
subCommissionSecondPrice: 0 // 二级分销的佣金
}
]) // 批量添加时的临时数据
// TODO @puhui999保存时每个商品规格的表单要校验下。例如说销售金额最低是 0.01 这种。
/** 批量添加 */
const batchAdd = () => {
formData.value.skus.forEach((item) => {
copyValueToTarget(item, skuList.value[0])
})
}
/** 删除 sku */
const deleteSku = (row) => {
const index = formData.value.skus.findIndex(
// 直接把列表转成字符串比较
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
)
formData.value.skus.splice(index, 1)
}
const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表头
/**
* 将传进来的值赋值给 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) => ({
propertyId: item.id,
propertyName: item.name,
valueId: v.id,
valueName: v.name
}))
)
// TODO @puhui是不是 buildSkuList这样容易理解一点哈。item 改成 sku
const buildList = build(propertyValues)
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
if (!validateData(propertyList)) {
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
formData.value!.skus = []
}
for (const item of buildList) {
const row = {
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象
price: 0,
marketPrice: 0,
costPrice: 0,
barCode: '',
picUrl: '',
stock: 0,
weight: 0,
volume: 0,
subCommissionFirstPrice: 0,
subCommissionSecondPrice: 0
}
// 如果存在属性相同的 sku 则不做处理
const index = formData.value!.skus.findIndex(
(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 = []
formData.value.skus.forEach((sku) =>
sku.properties
?.map((property) => property.propertyId)
.forEach((propertyId) => {
if (skuPropertyIds.indexOf(propertyId) === -1) {
skuPropertyIds.push(propertyId)
}
})
)
const propertyIds = propertyList.map((item) => item.id)
return skuPropertyIds.length === propertyIds.length
}
/** 构建所有排列组合 */
const build = (propertyValuesList: Property[][]) => {
if (propertyValuesList.length === 0) {
return []
} else if (propertyValuesList.length === 1) {
return propertyValuesList[0]
} else {
const result: Property[][] = []
const rest = build(propertyValuesList.slice(1))
for (let i = 0; i < propertyValuesList[0].length; i++) {
for (let j = 0; j < rest.length; j++) {
// 第一次不是数组结构,后面的都是数组结构
if (Array.isArray(rest[j])) {
result.push([propertyValuesList[0][i], ...rest[j]])
} else {
result.push([propertyValuesList[0][i], rest[j]])
}
}
}
return result
}
}
/** 监听属性列表,生成相关参数和表头 */
watch(
() => props.propertyList,
(propertyList) => {
// 如果不是多规格则结束
if (!formData.value.specType) {
return
}
// 如果当前组件作为批量添加数据使用,则重置表数据
if (props.isBatch) {
skuList.value = [
{
price: 0,
marketPrice: 0,
costPrice: 0,
barCode: '',
picUrl: '',
stock: 0,
weight: 0,
volume: 0,
subCommissionFirstPrice: 0,
subCommissionSecondPrice: 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.length === 0)) {
return
}
// 生成 table 数据,即 sku 列表
generateTableData(propertyList)
},
{
deep: true,
immediate: true
}
)
// 暴露出生成 sku 方法,给添加属性成功时调用
defineExpose({ generateTableData })
</script>

View File

@@ -0,0 +1,15 @@
import BasicInfoForm from './BasicInfoForm.vue'
import DescriptionForm from './DescriptionForm.vue'
import OtherSettingsForm from './OtherSettingsForm.vue'
import ProductAttributes from './ProductAttributes.vue'
import ProductAttributesAddForm from './ProductAttributesAddForm.vue'
import SkuList from './SkuList.vue'
export {
BasicInfoForm,
DescriptionForm,
OtherSettingsForm,
ProductAttributes,
ProductAttributesAddForm,
SkuList
}

View File

@@ -0,0 +1,422 @@
<template>
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="68px"
>
<!-- TODO @puhui999品牌应该是数据下拉哈 -->
<el-form-item label="品牌名称" prop="name">
<el-input
v-model="queryParams.name"
class="!w-240px"
clearable
placeholder="请输入品牌名称"
@keyup.enter="handleQuery"
/>
</el-form-item>
<!-- TODO 分类只能选择二级分类目前还没做还是先以联调通顺为主 -->
<!-- TODO puhui999我们要不改成支持选择一级如果选择一级后端要递归查询下子分类然后去 in -->
<el-form-item label="商品分类" prop="categoryId">
<el-tree-select
v-model="queryParams.categoryId"
:data="categoryList"
:props="defaultProps"
check-strictly
class="w-1/1"
node-key="id"
placeholder="请选择商品分类"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
<el-button v-hasPermi="['product:spu:create']" plain type="primary" @click="openForm">
<Icon class="mr-5px" icon="ep:plus" />
新增
</el-button>
<el-button
v-hasPermi="['product:spu:export']"
:loading="exportLoading"
plain
type="success"
@click="handleExport"
>
<Icon class="mr-5px" icon="ep:download" />
导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-tabs v-model="queryParams.tabType" @tab-click="handleTabClick">
<el-tab-pane
v-for="item in tabsData"
:key="item.type"
:label="item.name + '(' + item.count + ')'"
:name="item.type"
/>
</el-tabs>
<el-table v-loading="loading" :data="list">
<!-- TODO puhui这几个属性哈一行三个
商品分类服装鞋包/箱包
商品市场价格100.00
成本价0.00
收藏5
虚拟销量999 -->
<el-table-column type="expand" width="30">
<template #default="{ row }">
<el-form class="demo-table-expand" inline label-position="left">
<el-form-item label="市场价:">
<span>{{ formatToFraction(row.marketPrice) }}</span>
</el-form-item>
<el-form-item label="成本价:">
<span>{{ formatToFraction(row.costPrice) }}</span>
</el-form-item>
<el-form-item label="虚拟销量:">
<span>{{ row.virtualSalesCount }}</span>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column key="id" align="center" label="商品编号" prop="id" />
<el-table-column label="商品图" min-width="80">
<template #default="{ row }">
<el-image :src="row.picUrl" @click="imagePreview(row.picUrl)" class="w-30px h-30px" />
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
<el-table-column align="center" label="商品售价" min-width="90" prop="price">
<template #default="{ row }">
{{ formatToFraction(row.price) }}
</template>
</el-table-column>
<el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
<el-table-column align="center" label="库存" min-width="90" prop="stock" />
<el-table-column align="center" label="排序" min-width="70" prop="sort" />
<el-table-column
:formatter="dateFormatter"
align="center"
label="创建时间"
prop="createTime"
width="180"
/>
<el-table-column align="center" label="状态" min-width="80">
<template #default="{ row }">
<template v-if="row.status >= 0">
<el-switch
v-model="row.status"
:active-value="1"
:inactive-value="0"
active-text="上架"
inactive-text="下架"
inline-prompt
@change="changeStatus(row)"
/>
</template>
<template v-else>
<el-tag type="info">回收站</el-tag>
</template>
</template>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作" min-width="200">
<template #default="{ row }">
<!-- TODO @puhui999详情可以后面点做哈 -->
<el-button v-hasPermi="['product:spu:update']" link type="primary" @click="openDetail">
详情
</el-button>
<template v-if="queryParams.tabType === 4">
<el-button
v-hasPermi="['product:spu:delete']"
link
type="danger"
@click="handleDelete(row.id)"
>
删除
</el-button>
<el-button
v-hasPermi="['product:spu:update']"
link
type="primary"
@click="changeStatus(row, ProductSpuStatusEnum.DISABLE.status)"
>
恢复到仓库
</el-button>
</template>
<template v-else>
<!-- 只有不是上架和回收站的商品可以编辑 -->
<el-button
v-if="queryParams.tabType !== 0"
v-hasPermi="['product:spu:update']"
link
type="primary"
@click="openForm(row.id)"
>
修改
</el-button>
<el-button
v-hasPermi="['product:spu:update']"
link
type="primary"
@click="changeStatus(row, ProductSpuStatusEnum.RECYCLE.status)"
>
加入回收站
</el-button>
</template>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
</template>
<script lang="ts" name="ProductSpu" setup>
import { TabsPaneContext } from 'element-plus'
import { cloneDeep } from 'lodash-es'
import { createImageViewer } from '@/components/ImageViewer'
import { dateFormatter } from '@/utils/formatTime'
import { defaultProps, handleTree } from '@/utils/tree'
import { ProductSpuStatusEnum } from '@/utils/constants'
import { formatToFraction } from '@/utils'
import download from '@/utils/download'
import * as ProductSpuApi from '@/api/mall/product/spu'
import * as ProductCategoryApi from '@/api/mall/product/category'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const { currentRoute, push } = useRouter() // 路由跳转
const loading = ref(false) // 列表的加载中
const exportLoading = ref(false) // 导出的加载中
const total = ref(0) // 列表的总页数
const list = ref<any[]>([]) // 列表的数据
// tabs 数据
const tabsData = ref([
{
count: 0,
name: '出售中商品',
type: 0
},
{
count: 0,
name: '仓库中商品',
type: 1
},
{
count: 0,
name: '已经售空商品',
type: 2
},
{
count: 0,
name: '警戒库存',
type: 3
},
{
count: 0,
name: '商品回收站',
type: 4
}
])
/** 获得每个 Tab 的数量 */
const getTabsCount = async () => {
const res = await ProductSpuApi.getTabsCount()
for (let objName in res) {
tabsData.value[Number(objName)].count = res[objName]
}
}
const queryParams = ref({
pageNo: 1,
pageSize: 10,
tabType: 0
}) // 查询参数
const queryFormRef = ref() // 搜索的表单Ref
const handleTabClick = (tab: TabsPaneContext) => {
queryParams.value.tabType = tab.paneName
getList()
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProductSpuApi.getSpuPage(queryParams.value)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/**
* 更改 SPU 状态
*
* @param row
* @param status 更改前的值
*/
const changeStatus = async (row, status?: number) => {
const deepCopyValue = cloneDeep(unref(row))
if (typeof status !== 'undefined') deepCopyValue.status = status
try {
let text = ''
switch (deepCopyValue.status) {
case ProductSpuStatusEnum.DISABLE.status:
text = ProductSpuStatusEnum.DISABLE.name
break
case ProductSpuStatusEnum.ENABLE.status:
text = ProductSpuStatusEnum.ENABLE.name
break
case ProductSpuStatusEnum.RECYCLE.status:
text = `加入${ProductSpuStatusEnum.RECYCLE.name}`
break
}
await message.confirm(
deepCopyValue.status === -1
? `确认要将[${row.name}]${text}吗?`
: row.status === -1 // 再判断一次原对象是否等于-1例: 把回收站中的商品恢复到仓库中事件触发时原对象status为-1 深拷贝对象status被赋值为0
? `确认要将[${row.name}]恢复到仓库吗?`
: `确认要${text}[${row.name}]吗?`
)
await ProductSpuApi.updateStatus({ id: deepCopyValue.id, status: deepCopyValue.status })
message.success('更新状态成功')
// 刷新 tabs 数据
await getTabsCount()
// 刷新列表
await getList()
} catch {
// 取消更改状态时回显数据
row.status =
row.status === ProductSpuStatusEnum.DISABLE.status
? ProductSpuStatusEnum.ENABLE.status
: ProductSpuStatusEnum.DISABLE.status
}
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await ProductSpuApi.deleteSpu(id)
message.success(t('common.delSuccess'))
// 刷新tabs数据
await getTabsCount()
// 刷新列表
await getList()
} catch {}
}
/** 商品图预览 */
const imagePreview = (imgUrl: string) => {
createImageViewer({
urlList: [imgUrl]
})
}
/** 搜索按钮操作 */
const handleQuery = () => {
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/**
* 新增或修改
*
* @param id 商品 SPU 编号
*/
const openForm = (id?: number) => {
// 修改
if (typeof id === 'number') {
push('/product/productSpuEdit/' + id)
return
}
// 新增
push('/product/productSpuAdd')
}
/**
* 查看商品详情
*/
const openDetail = () => {
message.alert('查看详情未完善!!!')
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await ProductSpuApi.exportSpu(queryParams)
download.excel(data, '商品列表.xls')
} catch {
} finally {
exportLoading.value = false
}
}
// 监听路由变化更新列表 TODO @puhui999这个是必须加的么fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新
watch(
() => currentRoute.value,
() => {
getList()
}
)
const categoryList = ref() // 分类树
/** 初始化 **/
onMounted(async () => {
await getTabsCount()
await getList()
// 获得分类树
const data = await ProductCategoryApi.getCategoryList({})
categoryList.value = handleTree(data, 'id', 'parentId')
})
</script>
<style lang="scss" scoped>
.demo-table-expand {
padding-left: 42px;
:deep(.el-form-item__label) {
width: 82px;
font-weight: bold;
color: #99a9bf;
}
}
</style>

View File

@@ -0,0 +1,124 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
>
<el-form-item label="快递公司编码" prop="code">
<el-input v-model="formData.code" placeholder="请输入快递编码" />
</el-form-item>
<el-form-item label="快递公司名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入快递名称" />
</el-form-item>
<el-form-item label="快递公司 logo" prop="logo">
<UploadImg v-model="formData.logo" :limit="1" :is-show-tip="false" />
<div style="font-size: 10px" class="pl-10px">推荐 180x180 图片分辨率</div>
</el-form-item>
<el-form-item label="分类排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="开启状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts" name="ExpressForm">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
import * as DeliveryExpressApi from '@/api/mall/trade/delivery/express'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({
id: undefined,
code: '',
name: '',
logo: '',
sort: 0,
status: CommonStatusEnum.ENABLE
})
const formRules = reactive({
code: [{ required: true, message: '快递编码不能为空', trigger: 'blur' }],
name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
logo: [{ required: true, message: '分类图片不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }],
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await DeliveryExpressApi.getDeliveryExpress(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formData.value as DeliveryExpressApi.DeliveryExpressVO
if (formType.value === 'create') {
await DeliveryExpressApi.createDeliveryExpress(data)
message.success(t('common.createSuccess'))
} else {
await DeliveryExpressApi.updateDeliveryExpress(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: '',
picUrl: '',
bigPicUrl: '',
status: CommonStatusEnum.ENABLE
}
formRef.value?.resetFields()
}
</script>

View File

@@ -0,0 +1,184 @@
<template>
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="100px"
>
<el-form-item label="快递公司编号" prop="code">
<el-input
v-model="queryParams.code"
placeholder="请输快递公司编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="快递公司名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输快递公司名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['trade:delivery:express:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['trade:delivery:express:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="快递公司编号" prop="code" />
<el-table-column label="快递公司名称" prop="name" />
<el-table-column label="快递公司 logo " prop="logo">
<template #default="scope">
<img v-if="scope.row.logo" :src="scope.row.logo" alt="快递公司logo" class="h-100px" />
</template>
</el-table-column>
<el-table-column label="排序" align="center" prop="sort" />
<el-table-column label="开启状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['trade:delivery:express:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['trade:delivery:express:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<ExpressForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="Express">
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as DeliveryExpressApi from '@/api/mall/trade/delivery/express'
import ExpressForm from './ExpressForm.vue'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const total = ref(0) // 列表的总页数
const loading = ref(true) // 列表的加载中
const list = ref<any[]>([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
code: '',
name: ''
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await DeliveryExpressApi.getDeliveryExpressPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await DeliveryExpressApi.deleteDeliveryExpress(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await DeliveryExpressApi.exportDeliveryExpressApi(queryParams)
download.excel(data, '快递公司.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@@ -0,0 +1,405 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="80%">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="模板名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入模板名称" />
</el-form-item>
<el-form-item label="计费方式" prop="chargeMode">
<el-radio-group v-model="formData.chargeMode" @change="changeChargeMode">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.EXPRESS_CHARGE_MODE)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="运费" prop="templateCharge">
<el-table border style="width: 100%" :data="formData.templateCharge">
<el-table-column align="center" label="区域" width="180">
<template #default="{ row }">
<!-- 区域数据太多用赖加载方式要不然性能有问题 -->
<el-tree-select
v-model="row.areaIds"
lazy
:load="loadChargeArea"
:props="defaultProps"
multiple
node-key="id"
check-strictly
show-checkbox
check-on-click-node
:render-after-expand="false"
:cache-data="areaCache"
/>
</template>
</el-table-column>
<el-table-column
align="center"
:label="columnTitle.startCountTitle"
width="180"
prop="startCount"
>
<template #default="{ row }">
<el-input-number v-model="row.startCount" :min="1" />
</template>
</el-table-column>
<el-table-column width="180" align="center" label="运费(元)" prop="startPrice">
<template #default="{ row }">
<el-input-number v-model="row.startPrice" :min="1" />
</template>
</el-table-column>
<el-table-column
width="180"
align="center"
:label="columnTitle.extraCountTitle"
prop="extraCount"
>
<template #default="{ row }">
<el-input-number v-model="row.extraCount" :min="1" />
</template>
</el-table-column>
<el-table-column width="180" align="center" label="续费(元)" prop="extraPrice">
<template #default="{ row }">
<el-input-number v-model="row.extraPrice" :min="1" />
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button link type="danger" @click="deleteChargeArea(scope.$index)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="addChargeArea()">
<Icon icon="ep:plus" class="mr-5px" /> 添加区域
</el-button>
</el-form-item>
<el-form-item label="包邮区域" prop="templateFree">
<el-table border style="width: 100%" :data="formData.templateFree">
<el-table-column align="center" label="区域">
<template #default="{ row }">
<!-- 区域数据太多用赖加载方式要不然性能有问题 -->
<el-tree-select
v-model="row.areaIds"
multiple
lazy
:load="loadFreeArea"
:props="defaultProps"
node-key="id"
check-strictly
show-checkbox
check-on-click-node
:render-after-expand="true"
:cache-data="areaCache"
/>
</template>
</el-table-column>
<el-table-column align="center" :label="columnTitle.freeCountTitle" prop="freeCount">
<template #default="{ row }">
<el-input-number v-model="row.freeCount" :min="1" />
</template>
</el-table-column>
<el-table-column align="center" label="包邮金额(元)" prop="freePrice">
<template #default="{ row }">
<el-input-number v-model="row.freePrice" :min="1" />
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button link type="danger" @click="deleteFreeArea(scope.$index)"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="addFreeArea()">
<Icon icon="ep:plus" class="mr-5px" /> 添加区域
</el-button>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" :min="0" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as DeliveryExpressTemplateApi from '@/api/mall/trade/delivery/expressTemplate'
import { defaultProps } from '@/utils/tree'
import { yuanToFen, fenToYuan } from '@/utils'
import { getChildrenArea, getAreaListByIds } from '@/api/system/area'
import { cloneDeep } from 'lodash-es'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({
id: undefined,
name: '',
chargeMode: 1,
sort: 0,
templateCharge: [],
templateFree: []
})
const columnTitleMap = new Map()
const columnTitle = ref({
startCountTitle: '首件',
extraCountTitle: '续件',
freeCountTitle: '包邮件数'
})
const formRules = reactive({
name: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }],
chargeMode: [{ required: true, message: '配送计费方式不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
const areaCache = ref([]) //由于区域节点懒加载,已选区域节点需要缓存展示
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
try {
// 修改时,设置数据
if (id) {
formLoading.value = true
formData.value = await DeliveryExpressTemplateApi.getDeliveryExpressTemplate(id)
columnTitle.value = columnTitleMap.get(formData.value.chargeMode)
const chargeAreaIds = []
const freeAreaIds = []
formData.value.templateCharge.forEach((item) => {
for (let i = 0; i < item.areaIds.length; i++) {
if (!chargeAreaIds.includes(item.areaIds[i])) {
chargeAreaIds.push(item.areaIds[i])
}
}
//前端价格以元展示
item.startPrice = fenToYuan(item.startPrice)
item.extraPrice = fenToYuan(item.extraPrice)
})
formData.value.templateFree.forEach((item) => {
for (let i = 0; i < item.areaIds.length; i++) {
if (!chargeAreaIds.includes(item.areaIds[i]) && !freeAreaIds.includes(item.areaIds[i])) {
freeAreaIds.push(item.areaIds[i])
}
}
item.freePrice = fenToYuan(item.freePrice)
})
//已选的区域节点
const areaIds = chargeAreaIds.concat(freeAreaIds)
//区域节点,懒加载方式。 已选节点需要缓存展示
areaCache.value = await getAreaListByIds(areaIds.join(','))
}
} finally {
formLoading.value = false
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formData.value as DeliveryExpressTemplateApi.DeliveryExpressTemplateVO
data.templateCharge.forEach((item) => {
//前端价格以元展示,提交到后端。用分计算
item.startPrice = yuanToFen(item.startPrice)
item.extraPrice = yuanToFen(item.extraPrice)
})
data.templateFree.forEach((item) => {
item.freePrice = yuanToFen(item.freePrice)
})
if (formType.value === 'create') {
await DeliveryExpressTemplateApi.createDeliveryExpressTemplate(data)
message.success(t('common.createSuccess'))
} else {
await DeliveryExpressTemplateApi.updateDeliveryExpressTemplate(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: '',
chargeMode: 1,
templateCharge: [
{
areaIds: [1],
startCount: 2,
startPrice: 5,
extraCount: 5,
extraPrice: 10
}
],
templateFree: [],
sort: 0
}
columnTitle.value = columnTitleMap.get(1)
formRef.value?.resetFields()
}
/** 配送计费方法改变 */
const changeChargeMode = (chargeMode: number) => {
columnTitle.value = columnTitleMap.get(chargeMode)
}
const defaultArea = [{ id: 1, name: '全国', disabled: false }]
/** 初始化数据 */
const initData = async () => {
// TODO 从服务端全量加载数据, 后面看懒加载是不是可以从前端获取数据。 目前从后端获取数据
// formLoading.value = true
// try {
// const data = await getAreaTree()
// areaTree = data
// console.log('areaTree', areaTree)
// } finally {
// formLoading.value = false
// }
//表头标题和计费方式的映射
columnTitleMap.set(1, {
startCountTitle: '首件',
extraCountTitle: '续件',
freeCountTitle: '包邮件数'
})
columnTitleMap.set(2, {
startCountTitle: '首件重量(kg)',
extraCountTitle: '续件重量(kg)',
freeCountTitle: '包邮重量(kg)'
})
columnTitleMap.set(3, {
startCountTitle: '首件体积(m³)',
extraCountTitle: '续件体积(m³)',
freeCountTitle: '包邮体积(m³)'
})
}
/** 懒加载运费区域树 */
const loadChargeArea = async (node, resolve) => {
//已选区域需要禁止再次选择
const areaIds = []
formData.value.templateCharge.forEach((item) => {
if (item.areaIds.length > 0) {
item.areaIds.forEach((areaId) => areaIds.push(areaId))
}
})
if (node.isLeaf) return resolve([])
const length = node.data.length
if (length === 0) {
const data = cloneDeep(defaultArea)
const item = data[0]
if (areaIds.includes(item.id)) {
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
//item.disabled = true
}
resolve(data)
} else {
const id = node.data.id
const data = await getChildrenArea(id)
data.forEach((item) => {
if (areaIds.includes(item.id)) {
//item.disabled = true
}
})
resolve(data)
}
}
/** 懒加载包邮区域树 */
const loadFreeArea = async (node, resolve) => {
if (node.isLeaf) return resolve([])
//已选区域需要禁止再次选择
const areaIds = []
formData.value.templateFree.forEach((item) => {
if (item.areaIds.length > 0) {
item.areaIds.forEach((areaId) => areaIds.push(areaId))
}
})
const length = node.data.length
if (length === 0) {
// 为空,从全国开始选择。全国 id == 1
const data = cloneDeep(defaultArea)
const item = data[0]
if (areaIds.includes(item.id)) {
//item.disabled = true
}
resolve(data)
} else {
const id = node.data.id
const data = await getChildrenArea(id)
//已选区域需要禁止再次选择
data.forEach((item) => {
if (areaIds.includes(item.id)) {
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
//item.disabled = true
}
})
resolve(data)
}
}
/** 添加计费区域 */
const addChargeArea = () => {
const data = formData.value
data.templateCharge.push({
areaIds: [],
startCount: 1,
startPrice: 1,
extraCount: 1,
extraPrice: 1
})
}
/** 删除计费区域 */
const deleteChargeArea = (index) => {
const data = formData.value
data.templateCharge.splice(index, 1)
}
/** 添加包邮区域 */
const addFreeArea = () => {
const data = formData.value
data.templateFree.push({
areaIds: [],
freeCount: 1,
freePrice: 1
})
}
/** 删除包邮区域 */
const deleteFreeArea = (index) => {
const data = formData.value
data.templateFree.splice(index, 1)
}
/** 初始化 **/
onMounted(() => {
initData()
})
</script>

View File

@@ -0,0 +1,160 @@
<template>
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="100px"
>
<el-form-item label="模板名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入模板名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="计费方式" prop="chargeMode">
<el-select
v-model="queryParams.chargeMode"
placeholder="计费方式"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.EXPRESS_CHARGE_MODE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['trade:delivery:express-template:create']"
>
<Icon icon="ep:plus" class="mr-5px" />
新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" prop="id" />
<el-table-column label="模板名称" prop="name" />
<el-table-column label="计费方式" prop="chargeMode" align="center">
<template #default="scope">
<dict-tag :type="DICT_TYPE.EXPRESS_CHARGE_MODE" :value="scope.row.chargeMode" />
</template>
</el-table-column>
<el-table-column label="排序" prop="sort" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['trade:delivery:express-template:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['trade:delivery:express-template:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<ExpressTemplateForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="DeliveryExpressTemplate">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as DeliveryExpressTemplateApi from '@/api/mall/trade/delivery/expressTemplate'
import ExpressTemplateForm from './ExpressTemplateForm.vue'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const total = ref(0) // 列表的总页数
const loading = ref(true) // 列表的加载中
const list = ref<any[]>([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: '',
chargeMode: undefined
})
const queryFormRef = ref() // 搜索的表单
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await DeliveryExpressTemplateApi.getDeliveryExpressTemplatePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await DeliveryExpressTemplateApi.deleteDeliveryExpressTemplate(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>