Commit ec3b028d by xiaobai

sync dev-20230606

parents 985d764f 70c50827
...@@ -102,7 +102,8 @@ const include = [ ...@@ -102,7 +102,8 @@ const include = [
'element-plus/es/components/timeline-item/style/css', 'element-plus/es/components/timeline-item/style/css',
'element-plus/es/components/collapse/style/css', 'element-plus/es/components/collapse/style/css',
'element-plus/es/components/collapse-item/style/css', 'element-plus/es/components/collapse-item/style/css',
'element-plus/es/components/button-group/style/css' 'element-plus/es/components/button-group/style/css',
'element-plus/es/components/text/style/css'
] ]
const exclude = ['@iconify/json'] const exclude = ['@iconify/json']
......
...@@ -54,3 +54,8 @@ export const getBrand = (id: number) => { ...@@ -54,3 +54,8 @@ export const getBrand = (id: number) => {
export const getBrandParam = (params: PageParam) => { export const getBrandParam = (params: PageParam) => {
return request.get({ url: '/product/brand/page', params }) return request.get({ url: '/product/brand/page', params })
} }
// 获得商品品牌精简信息列表
export const getSimpleBrandList = () => {
return request.get({ url: '/product/brand/list-all-simple' })
}
import request from '@/config/axios'
import type { SpuType } from './type/spuType' // TODO @puhui999: type 和 api 一起放,简单一点哈~
// TODO @puhui999:中英文之间有空格
// 获得spu列表 TODO @puhui999:这个是 getSpuPage 哈
export const getSpuList = (params: PageParam) => {
return request.get({ url: '/product/spu/page', params })
}
// 获得spu列表tabsCount
export const getTabsCount = () => {
return request.get({ url: '/product/spu/tabsCount' })
}
// 创建商品spu
export const createSpu = (data: SpuType) => {
return request.post({ url: '/product/spu/create', data })
}
// 更新商品spu
export const updateSpu = (data: SpuType) => {
return request.put({ url: '/product/spu/update', data })
}
// 更新商品spu status
export const updateStatus = (data: { id: number; status: number }) => {
return request.put({ url: '/product/spu/updateStatus', data })
}
// 获得商品 spu
export const getSpu = (id: number) => {
return request.get({ url: `/product/spu/get-detail?id=${id}` })
}
// 删除商品Spu
export const deleteSpu = (id: number) => {
return request.delete({ url: `/product/spu/delete?id=${id}` })
}
export interface Property {
/**
* 属性编号
*
* 关联 {@link ProductPropertyDO#getId()}
*/
propertyId?: number
/**
* 属性值编号
*
* 关联 {@link ProductPropertyValueDO#getId()}
*/
valueId?: number
/**
* 属性值名称
*/
valueName?: string
}
export interface SkuType {
/**
* 商品 SKU 编号,自增
*/
id?: number
/**
* SPU 编号
*/
spuId?: number
/**
* 属性数组,JSON 格式
*/
properties?: Property[]
/**
* 商品价格,单位:分
*/
price?: number
/**
* 市场价,单位:分
*/
marketPrice?: number
/**
* 成本价,单位:分
*/
costPrice?: number
/**
* 商品条码
*/
barCode?: string
/**
* 图片地址
*/
picUrl?: string
/**
* 库存
*/
stock?: number
/**
* 商品重量,单位:kg 千克
*/
weight?: number
/**
* 商品体积,单位:m^3 平米
*/
volume?: number
/**
* 一级分销的佣金,单位:分
*/
subCommissionFirstPrice?: number
/**
* 二级分销的佣金,单位:分
*/
subCommissionSecondPrice?: number
/**
* 商品销量
*/
salesCount?: number
}
import { SkuType } from './skuType'
export interface SpuType {
id?: number
name?: string // 商品名称
categoryId?: number | null // 商品分类
keyword?: string // 关键字
unit?: number | null // 单位
picUrl?: string // 商品封面图
sliderPicUrls?: string[] // 商品轮播图
introduction?: string // 商品简介
deliveryTemplateId?: number // 运费模版
specType?: boolean // 商品规格
subCommissionType?: boolean // 分销类型
skus: SkuType[] // sku数组
description?: string // 商品详情
sort?: string // 商品排序
giveIntegral?: number // 赠送积分
virtualSalesCount?: number // 虚拟销量
recommendHot?: boolean // 是否热卖
recommendBenefit?: boolean // 是否优惠
recommendBest?: boolean // 是否精品
recommendNew?: boolean // 是否新品
recommendGood?: boolean // 是否优品
}
import request from '@/config/axios'
export interface Property {
propertyId?: number // 属性编号
propertyName?: string // 属性名称
valueId?: number // 属性值编号
valueName?: string // 属性值名称
}
// TODO puhui999:是不是直接叫 Sku 更简洁一点哈。type 待后面,总感觉有个类型?
export interface SkuType {
id?: number // 商品 SKU 编号
spuId?: number // SPU 编号
properties?: Property[] // 属性数组
price?: number // 商品价格
marketPrice?: number // 市场价
costPrice?: number // 成本价
barCode?: string // 商品条码
picUrl?: string // 图片地址
stock?: number // 库存
weight?: number // 商品重量,单位:kg 千克
volume?: number // 商品体积,单位:m^3 平米
subCommissionFirstPrice?: number // 一级分销的佣金
subCommissionSecondPrice?: number // 二级分销的佣金
salesCount?: number // 商品销量
}
// TODO puhui999:是不是直接叫 Spu 更简洁一点哈。type 待后面,总感觉有个类型?
export interface SpuType {
id?: number
name?: string // 商品名称
categoryId?: number | null // 商品分类
keyword?: string // 关键字
unit?: number | null // 单位
picUrl?: string // 商品封面图
sliderPicUrls?: string[] // 商品轮播图
introduction?: string // 商品简介
deliveryTemplateId?: number | null // 运费模版
brandId?: number | null // 商品品牌编号
specType?: boolean // 商品规格
subCommissionType?: boolean // 分销类型
skus: SkuType[] // sku数组
description?: string // 商品详情
sort?: string // 商品排序
giveIntegral?: number // 赠送积分
virtualSalesCount?: number // 虚拟销量
recommendHot?: boolean // 是否热卖
recommendBenefit?: boolean // 是否优惠
recommendBest?: boolean // 是否精品
recommendNew?: boolean // 是否新品
recommendGood?: boolean // 是否优品
}
// 获得 Spu 列表
export const getSpuPage = (params: PageParam) => {
return request.get({ url: '/product/spu/page', params })
}
// 获得 Spu 列表 tabsCount
export const getTabsCount = () => {
return request.get({ url: '/product/spu/get-count' })
}
// 创建商品 Spu
export const createSpu = (data: SpuType) => {
return request.post({ url: '/product/spu/create', data })
}
// 更新商品 Spu
export const updateSpu = (data: SpuType) => {
return request.put({ url: '/product/spu/update', data })
}
// 更新商品 Spu status
export const updateStatus = (data: { id: number; status: number }) => {
return request.put({ url: '/product/spu/update-status', data })
}
// 获得商品 Spu
export const getSpu = (id: number) => {
return request.get({ url: `/product/spu/get-detail?id=${id}` })
}
// 删除商品 Spu
export const deleteSpu = (id: number) => {
return request.delete({ url: `/product/spu/delete?id=${id}` })
}
// 导出商品 Spu Excel
export const exportSpu = async (params) => {
return await request.download({ url: '/product/spu/export', params })
}
import request from '@/config/axios'
export interface DeliveryExpressVO {
id: number
code: string
name: string
logo: string
sort: number
status: number
}
// 查询快递公司列表
export const getDeliveryExpressPage = async (params: PageParam) => {
return await request.get({ url: '/trade/delivery/express/page', params })
}
// 查询快递公司详情
export const getDeliveryExpress = async (id: number) => {
return await request.get({ url: '/trade/delivery/express/get?id=' + id })
}
// 新增快递公司
export const createDeliveryExpress = async (data: DeliveryExpressVO) => {
return await request.post({ url: '/trade/delivery/express/create', data })
}
// 修改快递公司
export const updateDeliveryExpress = async (data: DeliveryExpressVO) => {
return await request.put({ url: '/trade/delivery/express/update', data })
}
// 删除快递公司
export const deleteDeliveryExpress = async (id: number) => {
return await request.delete({ url: '/trade/delivery/express/delete?id=' + id })
}
// 导出快递公司 Excel
export const exportDeliveryExpressApi = async (params) => {
return await request.download({ url: '/trade/delivery/express/export-excel', params })
}
import request from '@/config/axios'
export interface DeliveryExpressTemplateVO {
id: number
name: string
chargeMode: number
sort: number
templateCharge: ExpressTemplateChargeVO[]
templateFree: ExpressTemplateFreeVO[]
}
export declare type ExpressTemplateChargeVO = {
areaIds: number[]
startCount: number
startPrice: number
extraCount: number
extraPrice: number
}
export declare type ExpressTemplateFreeVO = {
areaIds: number[]
freeCount: number
freePrice: number
}
// 查询快递运费模板列表
export const getDeliveryExpressTemplatePage = async (params: PageParam) => {
return await request.get({ url: '/trade/delivery/express-template/page', params })
}
// 查询快递运费模板详情
export const getDeliveryExpressTemplate = async (id: number) => {
return await request.get({ url: '/trade/delivery/express-template/get?id=' + id })
}
// 新增快递运费模板
export const createDeliveryExpressTemplate = async (data: DeliveryExpressTemplateVO) => {
return await request.post({ url: '/trade/delivery/express-template/create', data })
}
// 修改快递运费模板
export const updateDeliveryExpressTemplate = async (data: DeliveryExpressTemplateVO) => {
return await request.put({ url: '/trade/delivery/express-template/update', data })
}
// 删除快递运费模板
export const deleteDeliveryExpressTemplate = async (id: number) => {
return await request.delete({ url: '/trade/delivery/express-template/delete?id=' + id })
}
// 导出快递运费模板 Excel
export const exportDeliveryExpressTemplateApi = async (params) => {
return await request.download({ url: '/trade/delivery/express-template/export-excel', params })
}
...@@ -5,6 +5,14 @@ export const getAreaTree = async () => { ...@@ -5,6 +5,14 @@ export const getAreaTree = async () => {
return await request.get({ url: '/system/area/tree' }) return await request.get({ url: '/system/area/tree' })
} }
export const getChildrenArea = async (id: number) => {
return await request.get({ url: '/system/area/get-children?id=' + id })
}
export const getAreaListByIds = async (ids) => {
return await request.get({ url: '/system/area/get-by-ids?ids=' + ids })
}
// 获得 IP 对应的地区名 // 获得 IP 对应的地区名
export const getAreaByIp = async (ip: string) => { export const getAreaByIp = async (ip: string) => {
return await request.get({ url: '/system/area/get-by-ip?ip=' + ip }) return await request.get({ url: '/system/area/get-by-ip?ip=' + ip })
......
<template> <template>
<div class="upload-box"> <div class="upload-box">
<el-upload <el-upload
v-model:file-list="fileList"
:accept="fileType.join(',')"
:action="updateUrl" :action="updateUrl"
list-type="picture-card" :before-upload="beforeUpload"
:class="['upload', drag ? 'no-border' : '']" :class="['upload', drag ? 'no-border' : '']"
v-model:file-list="fileList" :drag="drag"
:multiple="true"
:limit="limit"
:headers="uploadHeaders" :headers="uploadHeaders"
:before-upload="beforeUpload" :limit="limit"
:multiple="true"
:on-error="uploadError"
:on-exceed="handleExceed" :on-exceed="handleExceed"
:on-success="uploadSuccess" :on-success="uploadSuccess"
:on-error="uploadError" list-type="picture-card"
:drag="drag"
:accept="fileType.join(',')"
> >
<div class="upload-empty"> <div class="upload-empty">
<slot name="empty"> <slot name="empty">
...@@ -40,15 +40,15 @@ ...@@ -40,15 +40,15 @@
</div> </div>
<el-image-viewer <el-image-viewer
v-if="imgViewVisible" v-if="imgViewVisible"
@close="imgViewVisible = false"
:url-list="[viewImageUrl]" :url-list="[viewImageUrl]"
@close="imgViewVisible = false"
/> />
</div> </div>
</template> </template>
<script setup lang="ts" name="UploadImgs"> <script lang="ts" name="UploadImgs" setup>
import { PropType } from 'vue' import { PropType } from 'vue'
import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'
import { ElNotification } from 'element-plus' import { ElNotification } from 'element-plus'
import type { UploadProps, UploadFile, UploadUserFile } from 'element-plus'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { getAccessToken, getTenantId } from '@/utils/auth' import { getAccessToken, getTenantId } from '@/utils/auth'
...@@ -88,8 +88,19 @@ const uploadHeaders = ref({ ...@@ -88,8 +88,19 @@ const uploadHeaders = ref({
'tenant-id': getTenantId() 'tenant-id': getTenantId()
}) })
const fileList = ref<UploadUserFile[]>(props.modelValue) const fileList = ref<UploadUserFile[]>()
// fix: 改为动态监听赋值解决图片回显问题
watch(
() => props.modelValue,
(data) => {
if (!data) return
fileList.value = data
},
{
deep: true,
immediate: true
}
)
/** /**
* @description 文件上传之前判断 * @description 文件上传之前判断
* @param rawFile 上传的文件 * @param rawFile 上传的文件
...@@ -116,9 +127,11 @@ const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => { ...@@ -116,9 +127,11 @@ const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
interface UploadEmits { interface UploadEmits {
(e: 'update:modelValue', value: UploadUserFile[]): void (e: 'update:modelValue', value: UploadUserFile[]): void
} }
const emit = defineEmits<UploadEmits>() const emit = defineEmits<UploadEmits>()
const uploadSuccess = (response, uploadFile: UploadFile) => { const uploadSuccess = (response, uploadFile: UploadFile) => {
if (!response) return if (!response) return
// TODO 多图上传组件成功后只是把保存成功后的url替换掉组件选图时的文件路径,所以返回的fileList包含的是一个包含文件信息的对象列表
uploadFile.url = response.data uploadFile.url = response.data
emit('update:modelValue', fileList.value) emit('update:modelValue', fileList.value)
message.success('上传成功') message.success('上传成功')
...@@ -159,35 +172,40 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => { ...@@ -159,35 +172,40 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
} }
</script> </script>
<style scoped lang="scss"> <style lang="scss" scoped>
.is-error { .is-error {
.upload { .upload {
:deep(.el-upload--picture-card), :deep(.el-upload--picture-card),
:deep(.el-upload-dragger) { :deep(.el-upload-dragger) {
border: 1px dashed var(--el-color-danger) !important; border: 1px dashed var(--el-color-danger) !important;
&:hover { &:hover {
border-color: var(--el-color-primary) !important; border-color: var(--el-color-primary) !important;
} }
} }
} }
} }
:deep(.disabled) { :deep(.disabled) {
.el-upload--picture-card, .el-upload--picture-card,
.el-upload-dragger { .el-upload-dragger {
cursor: not-allowed; cursor: not-allowed;
background: var(--el-disabled-bg-color) !important; background: var(--el-disabled-bg-color) !important;
border: 1px dashed var(--el-border-color-darker); border: 1px dashed var(--el-border-color-darker);
&:hover { &:hover {
border-color: var(--el-border-color-darker) !important; border-color: var(--el-border-color-darker) !important;
} }
} }
} }
.upload-box { .upload-box {
.no-border { .no-border {
:deep(.el-upload--picture-card) { :deep(.el-upload--picture-card) {
border: none !important; border: none !important;
} }
} }
:deep(.upload) { :deep(.upload) {
.el-upload-dragger { .el-upload-dragger {
display: flex; display: flex;
...@@ -199,14 +217,17 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => { ...@@ -199,14 +217,17 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
overflow: hidden; overflow: hidden;
border: 1px dashed var(--el-border-color-darker); border: 1px dashed var(--el-border-color-darker);
border-radius: v-bind(borderRadius); border-radius: v-bind(borderRadius);
&:hover { &:hover {
border: 1px dashed var(--el-color-primary); border: 1px dashed var(--el-color-primary);
} }
} }
.el-upload-dragger.is-dragover { .el-upload-dragger.is-dragover {
background-color: var(--el-color-primary-light-9); background-color: var(--el-color-primary-light-9);
border: 2px dashed var(--el-color-primary) !important; border: 2px dashed var(--el-color-primary) !important;
} }
.el-upload-list__item, .el-upload-list__item,
.el-upload--picture-card { .el-upload--picture-card {
width: v-bind(width); width: v-bind(width);
...@@ -214,11 +235,13 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => { ...@@ -214,11 +235,13 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
background-color: transparent; background-color: transparent;
border-radius: v-bind(borderRadius); border-radius: v-bind(borderRadius);
} }
.upload-image { .upload-image {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: contain; object-fit: contain;
} }
.upload-handle { .upload-handle {
position: absolute; position: absolute;
top: 0; top: 0;
...@@ -233,6 +256,7 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => { ...@@ -233,6 +256,7 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
background: rgb(0 0 0 / 60%); background: rgb(0 0 0 / 60%);
opacity: 0; opacity: 0;
transition: var(--el-transition-duration-fast); transition: var(--el-transition-duration-fast);
.handle-icon { .handle-icon {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -240,15 +264,18 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => { ...@@ -240,15 +264,18 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
justify-content: center; justify-content: center;
padding: 0 6%; padding: 0 6%;
color: aliceblue; color: aliceblue;
.el-icon { .el-icon {
margin-bottom: 15%; margin-bottom: 15%;
font-size: 140%; font-size: 140%;
} }
span { span {
font-size: 100%; font-size: 100%;
} }
} }
} }
.el-upload-list__item { .el-upload-list__item {
&:hover { &:hover {
.upload-handle { .upload-handle {
...@@ -256,6 +283,7 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => { ...@@ -256,6 +283,7 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
} }
} }
} }
.upload-empty { .upload-empty {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -263,12 +291,14 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => { ...@@ -263,12 +291,14 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
font-size: 12px; font-size: 12px;
line-height: 30px; line-height: 30px;
color: var(--el-color-info); color: var(--el-color-info);
.el-icon { .el-icon {
font-size: 28px; font-size: 28px;
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
} }
} }
} }
.el-upload__tip { .el-upload__tip {
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
......
...@@ -365,22 +365,35 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -365,22 +365,35 @@ const remainingRouter: AppRouteRecordRaw[] = [
{ {
path: '/product', path: '/product',
component: Layout, component: Layout,
name: 'ProductManagementEdit', name: 'Product',
meta: { meta: {
hidden: true hidden: true
}, },
children: [ children: [
{ {
path: 'productManagementAdd', // TODO @puhui999:最好拆成 add 和 edit 两个路由;添加商品;修改商品 path: 'productSpuAdd', // TODO @puhui999:最好拆成 add 和 edit 两个路由;添加商品;修改商品 fix
component: () => import('@/views/mall/product/spu/addForm.vue'), component: () => import('@/views/mall/product/spu/addForm.vue'),
name: 'ProductManagementAdd', name: 'ProductSpuAdd',
meta: { meta: {
noCache: true, noCache: true,
hidden: true, hidden: true,
canTo: true, canTo: true,
icon: 'ep:edit', icon: 'ep:edit',
title: '添加商品', title: '添加商品',
activeMenu: '/product/product-management' activeMenu: '/product/product-spu'
}
},
{
path: 'productSpuEdit/:spuId(\\d+)',
component: () => import('@/views/mall/product/spu/addForm.vue'),
name: 'productSpuEdit',
meta: {
noCache: true,
hidden: true,
canTo: true,
icon: 'ep:edit',
title: '编辑商品',
activeMenu: '/product/product-spu'
} }
} }
] ]
......
...@@ -149,6 +149,9 @@ export enum DICT_TYPE { ...@@ -149,6 +149,9 @@ export enum DICT_TYPE {
PRODUCT_UNIT = 'product_unit', // 商品单位 PRODUCT_UNIT = 'product_unit', // 商品单位
PRODUCT_SPU_STATUS = 'product_spu_status', //商品状态 PRODUCT_SPU_STATUS = 'product_spu_status', //商品状态
// ========== MALL 交易模块 ==========
EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode', //快递的计费方式
//===add by 20230530==== //===add by 20230530====
// ========== MALL - ORDER 模块 ========== // ========== MALL - ORDER 模块 ==========
TRADE_AFTER_SALE_STATUS = 'trade_after_sale_status', // 售后 - 状态 TRADE_AFTER_SALE_STATUS = 'trade_after_sale_status', // 售后 - 状态
......
...@@ -155,3 +155,57 @@ export const fileSizeFormatter = (row, column, cellValue) => { ...@@ -155,3 +155,57 @@ export const fileSizeFormatter = (row, column, cellValue) => {
const sizeStr = size.toFixed(2) //保留的小数位数 const sizeStr = size.toFixed(2) //保留的小数位数
return sizeStr + ' ' + unitArr[index] return sizeStr + ' ' + unitArr[index]
} }
/**
* 将值复制到目标对象,且以目标对象属性为准,例:target: {a:1} source:{a:2,b:3} 结果为:{a:2}
* @param target 目标对象
* @param source 源对象
*/
export const copyValueToTarget = (target, source) => {
const newObj = Object.assign({}, target, source)
// 删除多余属性
Object.keys(newObj).forEach((key) => {
// 如果不是target中的属性则删除
if (Object.keys(target).indexOf(key) === -1) {
delete newObj[key]
}
})
// 更新目标对象值
Object.assign(target, newObj)
}
// TODO @puhui999:返回要带上 .00 哈.例如说 1.00
/**
* 将一个整数转换为分数保留两位小数
* @param num
*/
export const formatToFraction = (num: number | string | undefined): number => {
if (typeof num === 'undefined') return 0
const parsedNumber = typeof num === 'string' ? parseFloat(num) : num
return parseFloat((parsedNumber / 100).toFixed(2))
}
/**
* 将一个分数转换为整数
* @param num
*/
export const convertToInteger = (num: number | string | undefined): number => {
if (typeof num === 'undefined') return 0
const parsedNumber = typeof num === 'string' ? parseFloat(num) : num
// TODO 分转元后还有小数则四舍五入
return Math.round(parsedNumber * 100)
}
/**
* 元转分
*/
export const yuanToFen = (amount: string | number): number => {
return Math.round(Number(amount) * 100)
}
/**
* 分转元
*/
export const fenToYuan = (amount: string | number): number => {
return Number((Number(amount) / 100).toFixed(2))
}
// TODO @puhui999:这个方法,可以考虑放到 index.js
/**
* 将值复制到目标对象,且以目标对象属性为准,例:target: {a:1} source:{a:2,b:3} 结果为:{a:2}
* @param target 目标对象
* @param source 源对象
*/
export const copyValueToTarget = (target, source) => {
const newObj = Object.assign({}, target, source)
// 删除多余属性
Object.keys(newObj).forEach((key) => {
// 如果不是target中的属性则删除
if (Object.keys(target).indexOf(key) === -1) {
delete newObj[key]
}
})
// 更新目标对象值
Object.assign(target, newObj)
}
...@@ -11,7 +11,8 @@ const DEFAULT_CONFIG: TreeHelperConfig = { ...@@ -11,7 +11,8 @@ const DEFAULT_CONFIG: TreeHelperConfig = {
export const defaultProps = { export const defaultProps = {
children: 'children', children: 'children',
label: 'name', label: 'name',
value: 'id' value: 'id',
isLeaf: 'leaf'
} }
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config) const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config)
......
<template> <template>
<el-form ref="ProductManagementBasicInfoRef" :model="formData" :rules="rules" label-width="120px"> <el-form ref="productSpuBasicInfoRef" :model="formData" :rules="rules" label-width="120px">
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="商品名称" prop="name"> <el-form-item label="商品名称" prop="name">
...@@ -14,9 +14,9 @@ ...@@ -14,9 +14,9 @@
:data="categoryList" :data="categoryList"
:props="defaultProps" :props="defaultProps"
check-strictly check-strictly
class="w-1/1"
node-key="id" node-key="id"
placeholder="请选择商品分类" placeholder="请选择商品分类"
class="w-1/1"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="单位" prop="unit"> <el-form-item label="单位" prop="unit">
<el-select v-model="formData.unit" placeholder="请选择单位" class="w-1/1"> <el-select v-model="formData.unit" class="w-1/1" placeholder="请选择单位">
<el-option <el-option
v-for="dict in getIntDictOptions(DICT_TYPE.PRODUCT_UNIT)" v-for="dict in getIntDictOptions(DICT_TYPE.PRODUCT_UNIT)"
:key="dict.value" :key="dict.value"
...@@ -54,18 +54,28 @@ ...@@ -54,18 +54,28 @@
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="商品轮播图" prop="sliderPicUrls"> <el-form-item label="商品轮播图" prop="sliderPicUrls">
<UploadImgs v-model="formData.sliderPicUrls" /> <UploadImgs v-model:modelValue="formData.sliderPicUrls" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="运费模板" prop="deliveryTemplateId"> <el-form-item label="运费模板" prop="deliveryTemplateId">
<el-select v-model="formData.deliveryTemplateId" placeholder="请选择" class="w-1/1"> <el-select v-model="formData.deliveryTemplateId" placeholder="请选择">
<el-option v-for="item in []" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in []" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
<el-button class="ml-20px">运费模板</el-button>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-button class="ml-20px">运费模板</el-button> <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>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="商品规格" props="specType"> <el-form-item label="商品规格" props="specType">
...@@ -86,36 +96,37 @@ ...@@ -86,36 +96,37 @@
<!-- 多规格添加--> <!-- 多规格添加-->
<el-col :span="24"> <el-col :span="24">
<el-form-item v-if="formData.specType" label="商品属性"> <el-form-item v-if="formData.specType" label="商品属性">
<!-- TODO @puhui999:参考 https://admin.java.crmeb.net/store/list/creatProduct 添加规格好做么?添加的时候,不用输入备注哈 --> <el-button class="mr-15px mb-10px" @click="attributesAddFormRef.open">添加规格</el-button>
<el-button class="mr-15px mb-10px" @click="AttributesAddFormRef.open">添加规格</el-button> <ProductAttributes :propertyList="propertyList" @success="generateSkus" />
<ProductAttributes :attribute-data="attributeList" />
</el-form-item> </el-form-item>
<template v-if="formData.specType && attributeList.length > 0"> <template v-if="formData.specType && propertyList.length > 0">
<el-form-item label="批量设置"> <el-form-item label="批量设置">
<SkuList :attributeList="attributeList" :is-batch="true" :prop-form-data="formData" /> <SkuList :is-batch="true" :prop-form-data="formData" :propertyList="propertyList" />
</el-form-item> </el-form-item>
<el-form-item label="属性列表"> <el-form-item label="属性列表">
<SkuList :attributeList="attributeList" :prop-form-data="formData" /> <SkuList ref="skuListRef" :prop-form-data="formData" :propertyList="propertyList" />
</el-form-item> </el-form-item>
</template> </template>
<el-form-item v-if="!formData.specType"> <el-form-item v-if="!formData.specType">
<SkuList :attributeList="attributeList" :prop-form-data="formData" /> <SkuList :prop-form-data="formData" :propertyList="propertyList" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
<ProductAttributesAddForm ref="AttributesAddFormRef" @success="addAttribute" /> <ProductAttributesAddForm ref="attributesAddFormRef" :propertyList="propertyList" />
</template> </template>
<script lang="ts" name="ProductManagementBasicInfoForm" setup> <script lang="ts" name="ProductSpuBasicInfoForm" setup>
import { PropType } from 'vue' import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import { defaultProps, handleTree } from '@/utils/tree' import { defaultProps, handleTree } from '@/utils/tree'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import type { SpuType } from '@/api/mall/product/management/type/spuType' import type { SpuType } from '@/api/mall/product/spu'
import { UploadImg, UploadImgs } from '@/components/UploadFile' import { UploadImg, UploadImgs } from '@/components/UploadFile'
import { copyValueToTarget } from '@/utils/object'
import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index' import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index'
import * as ProductCategoryApi from '@/api/mall/product/category' import * as ProductCategoryApi from '@/api/mall/product/category'
import { propTypes } from '@/utils/propTypes' import { getSimpleBrandList } from '@/api/mall/product/brand'
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const props = defineProps({ const props = defineProps({
...@@ -125,27 +136,24 @@ const props = defineProps({ ...@@ -125,27 +136,24 @@ const props = defineProps({
}, },
activeName: propTypes.string.def('') activeName: propTypes.string.def('')
}) })
const AttributesAddFormRef = ref() // 添加商品属性表单 TODO @puhui999:小写开头哈 const attributesAddFormRef = ref() // 添加商品属性表单
const ProductManagementBasicInfoRef = ref() // 表单Ref TODO @puhui999:小写开头哈 const productSpuBasicInfoRef = ref() // 表单 Ref
// TODO @puhui999:attributeList 改成 propertyList,会更统一一点 const propertyList = ref([]) // 商品属性列表
const attributeList = ref([]) // 商品属性列表 const skuListRef = ref() // 商品属性列表Ref
/** 添加商品属性 */ // TODO @puhui999:propFormData 算出来 /** 调用 SkuList generateTableData 方法*/
const addAttribute = (property: any) => { const generateSkus = (propertyList) => {
if (Array.isArray(property)) { skuListRef.value.generateTableData(propertyList)
attributeList.value = property
return
}
attributeList.value.push(property)
} }
const formData = reactive<SpuType>({ const formData = reactive<SpuType>({
name: '', // 商品名称 name: '', // 商品名称
categoryId: undefined, // 商品分类 categoryId: null, // 商品分类
keyword: '', // 关键字 keyword: '', // 关键字
unit: '', // 单位 unit: '', // 单位
picUrl: '', // 商品封面图 picUrl: '', // 商品封面图
sliderPicUrls: [], // 商品轮播图 sliderPicUrls: [], // 商品轮播图
introduction: '', // 商品简介 introduction: '', // 商品简介
deliveryTemplateId: 1, // 运费模版 deliveryTemplateId: 1, // 运费模版
brandId: null, // 商品品牌
specType: false, // 商品规格 specType: false, // 商品规格
subCommissionType: false, // 分销类型 subCommissionType: false, // 分销类型
skus: [] skus: []
...@@ -159,6 +167,7 @@ const rules = reactive({ ...@@ -159,6 +167,7 @@ const rules = reactive({
picUrl: [required], picUrl: [required],
sliderPicUrls: [required], sliderPicUrls: [required],
// deliveryTemplateId: [required], // deliveryTemplateId: [required],
brandId: [required],
specType: [required], specType: [required],
subCommissionType: [required] subCommissionType: [required]
}) })
...@@ -169,11 +178,35 @@ const rules = reactive({ ...@@ -169,11 +178,35 @@ const rules = reactive({
watch( watch(
() => props.propFormData, () => props.propFormData,
(data) => { (data) => {
if (!data) return if (!data) {
return
}
copyValueToTarget(formData, data) copyValueToTarget(formData, data)
formData.sliderPicUrls = data['sliderPicUrls'].map((item) => ({
url: item
}))
// TODO @puhui999:if 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
}
}, },
{ {
deep: true,
immediate: true immediate: true
} }
) )
...@@ -184,8 +217,8 @@ watch( ...@@ -184,8 +217,8 @@ watch(
const emit = defineEmits(['update:activeName']) const emit = defineEmits(['update:activeName'])
const validate = async () => { const validate = async () => {
// 校验表单 // 校验表单
if (!ProductManagementBasicInfoRef) return if (!productSpuBasicInfoRef) return
return await unref(ProductManagementBasicInfoRef).validate((valid) => { return await unref(productSpuBasicInfoRef).validate((valid) => {
if (!valid) { if (!valid) {
message.warning('商品信息未完善!!') message.warning('商品信息未完善!!')
emit('update:activeName', 'basicInfo') emit('update:activeName', 'basicInfo')
...@@ -197,7 +230,7 @@ const validate = async () => { ...@@ -197,7 +230,7 @@ const validate = async () => {
} }
}) })
} }
defineExpose({ validate, addAttribute }) defineExpose({ validate })
/** 分销类型 */ /** 分销类型 */
const changeSubCommissionType = () => { const changeSubCommissionType = () => {
...@@ -211,7 +244,7 @@ const changeSubCommissionType = () => { ...@@ -211,7 +244,7 @@ const changeSubCommissionType = () => {
/** 选择规格 */ /** 选择规格 */
const onChangeSpec = () => { const onChangeSpec = () => {
// 重置商品属性列表 // 重置商品属性列表
attributeList.value = [] propertyList.value = []
// 重置sku列表 // 重置sku列表
formData.skus = [ formData.skus = [
{ {
...@@ -229,10 +262,13 @@ const onChangeSpec = () => { ...@@ -229,10 +262,13 @@ const onChangeSpec = () => {
] ]
} }
const categoryList = ref() // 分类树 const categoryList = ref([]) // 分类树
const brandList = ref([]) // 精简商品品牌列表
onMounted(async () => { onMounted(async () => {
// 获得分类树 // 获得分类树
const data = await ProductCategoryApi.getCategoryList({}) const data = await ProductCategoryApi.getCategoryList({})
categoryList.value = handleTree(data, 'id', 'parentId') categoryList.value = handleTree(data, 'id', 'parentId')
// 获取商品品牌列表
brandList.value = await getSimpleBrandList()
}) })
</script> </script>
<template> <template>
<el-form ref="DescriptionFormRef" :model="formData" :rules="rules" label-width="120px"> <el-form ref="descriptionFormRef" :model="formData" :rules="rules" label-width="120px">
<!--富文本编辑器组件--> <!--富文本编辑器组件-->
<el-form-item label="商品详情" prop="description"> <el-form-item label="商品详情" prop="description">
<Editor v-model:modelValue="formData.description" /> <Editor v-model:modelValue="formData.description" />
...@@ -7,11 +7,11 @@ ...@@ -7,11 +7,11 @@
</el-form> </el-form>
</template> </template>
<script lang="ts" name="DescriptionForm" setup> <script lang="ts" name="DescriptionForm" setup>
import type { SpuType } from '@/api/mall/product/management/type/spuType' import type { SpuType } from '@/api/mall/product/spu'
import { Editor } from '@/components/Editor' import { Editor } from '@/components/Editor'
import { PropType } from 'vue' import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils/object'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { copyValueToTarget } from '@/utils'
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const props = defineProps({ const props = defineProps({
...@@ -21,7 +21,7 @@ const props = defineProps({ ...@@ -21,7 +21,7 @@ const props = defineProps({
}, },
activeName: propTypes.string.def('') activeName: propTypes.string.def('')
}) })
const DescriptionFormRef = ref() // 表单Ref const descriptionFormRef = ref() // 表单Ref
const formData = ref<SpuType>({ const formData = ref<SpuType>({
description: '' // 商品详情 description: '' // 商品详情
}) })
...@@ -29,7 +29,6 @@ const formData = ref<SpuType>({ ...@@ -29,7 +29,6 @@ const formData = ref<SpuType>({
const rules = reactive({ const rules = reactive({
description: [required] description: [required]
}) })
/** /**
* 富文本编辑器如果输入过再清空会有残留,需再重置一次 * 富文本编辑器如果输入过再清空会有残留,需再重置一次
*/ */
...@@ -45,7 +44,6 @@ watch( ...@@ -45,7 +44,6 @@ watch(
immediate: true immediate: true
} }
) )
/** /**
* 将传进来的值赋值给formData * 将传进来的值赋值给formData
*/ */
...@@ -53,10 +51,11 @@ watch( ...@@ -53,10 +51,11 @@ watch(
() => props.propFormData, () => props.propFormData,
(data) => { (data) => {
if (!data) return if (!data) return
// fix:三个表单组件监听赋值必须使用 copyValueToTarget 使用 formData.value = data 会监听非常多次
copyValueToTarget(formData.value, data) copyValueToTarget(formData.value, data)
}, },
{ {
deep: true, // fix: 去掉深度监听只有对象引用发生改变的时候才执行,解决改一动多的问题
immediate: true immediate: true
} }
) )
...@@ -67,8 +66,8 @@ watch( ...@@ -67,8 +66,8 @@ watch(
const emit = defineEmits(['update:activeName']) const emit = defineEmits(['update:activeName'])
const validate = async () => { const validate = async () => {
// 校验表单 // 校验表单
if (!DescriptionFormRef) return if (!descriptionFormRef) return
return unref(DescriptionFormRef).validate((valid) => { return await unref(descriptionFormRef).validate((valid) => {
if (!valid) { if (!valid) {
message.warning('商品详情为完善!!') message.warning('商品详情为完善!!')
emit('update:activeName', 'description') emit('update:activeName', 'description')
......
<template> <template>
<el-form ref="OtherSettingsFormRef" :model="formData" :rules="rules" label-width="120px"> <el-form ref="otherSettingsFormRef" :model="formData" :rules="rules" label-width="120px">
<el-row> <el-row>
<!-- TODO @puhui999:横着三个哈 -->
<el-col :span="24"> <el-col :span="24">
<el-col :span="8"> <el-row :gutter="20">
<el-form-item label="商品排序" prop="sort"> <el-col :span="8">
<el-input-number v-model="formData.sort" :min="0" /> <el-form-item label="商品排序" prop="sort">
</el-form-item> <el-input-number v-model="formData.sort" :min="0" />
</el-col> </el-form-item>
<el-col :span="8"> </el-col>
<el-form-item label="赠送积分" prop="giveIntegral"> <el-col :span="8">
<el-input-number v-model="formData.giveIntegral" :min="0" /> <el-form-item label="赠送积分" prop="giveIntegral">
</el-form-item> <el-input-number v-model="formData.giveIntegral" :min="0" />
</el-col> </el-form-item>
<el-col :span="8"> </el-col>
<el-form-item label="虚拟销量" prop="virtualSalesCount"> <el-col :span="8">
<el-input-number <el-form-item label="虚拟销量" prop="virtualSalesCount">
v-model="formData.virtualSalesCount" <el-input-number
:min="0" v-model="formData.virtualSalesCount"
placeholder="请输入虚拟销量" :min="0"
/> placeholder="请输入虚拟销量"
</el-form-item> />
</el-col> </el-form-item>
</el-col>
</el-row>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="商品推荐"> <el-form-item label="商品推荐">
<el-checkbox-group v-model="checkboxGroup" @change="onChangeGroup"> <el-checkbox-group v-model="checkboxGroup" @change="onChangeGroup">
<el-checkbox v-for="(item, index) in recommend" :key="index" :label="item.value"> <el-checkbox v-for="(item, index) in recommendOptions" :key="index" :label="item.value">
{{ item.name }} {{ item.name }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<!-- TODO tag展示暂时不考虑排序 --> <!-- TODO tag展示暂时不考虑排序 -->
<el-form-item label="活动优先级"> <el-form-item label="活动优先级">
<el-tag>默认</el-tag> <el-tag>默认</el-tag>
<el-tag class="ml-2" type="success">秒杀</el-tag> <el-tag class="ml-2" type="success">秒杀</el-tag>
...@@ -51,10 +52,11 @@ ...@@ -51,10 +52,11 @@
</el-form> </el-form>
</template> </template>
<script lang="ts" name="OtherSettingsForm" setup> <script lang="ts" name="OtherSettingsForm" setup>
import type { SpuType } from '@/api/mall/product/management/type/spuType' import type { SpuType } from '@/api/mall/product/spu'
import { PropType } from 'vue' import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils/object'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { copyValueToTarget } from '@/utils'
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const props = defineProps({ const props = defineProps({
...@@ -64,35 +66,8 @@ const props = defineProps({ ...@@ -64,35 +66,8 @@ const props = defineProps({
}, },
activeName: propTypes.string.def('') activeName: propTypes.string.def('')
}) })
// 商品推荐选项 TODO @puhui999:这种叫 recommendOptions 会更合适哈
const recommend = [ const otherSettingsFormRef = ref() // 表单Ref
{ name: '是否热卖', value: 'recommendHot' },
{ name: '是否优惠', value: 'recommendBenefit' },
{ name: '是否精品', value: 'recommendBest' },
{ name: '是否新品', value: 'recommendNew' },
{ name: '是否优品', value: 'recommendGood' }
]
const checkboxGroup = ref<string[]>(['recommendHot']) // 选中推荐选项
/** 选择商品后赋值 */
const onChangeGroup = () => {
// TODO @puhui999:是不是可以遍历 recommend,然后进行是否选中;
checkboxGroup.value.includes('recommendHot')
? (formData.value.recommendHot = true)
: (formData.value.recommendHot = false)
checkboxGroup.value.includes('recommendBenefit')
? (formData.value.recommendBenefit = true)
: (formData.value.recommendBenefit = false)
checkboxGroup.value.includes('recommendBest')
? (formData.value.recommendBest = true)
: (formData.value.recommendBest = false)
checkboxGroup.value.includes('recommendNew')
? (formData.value.recommendNew = true)
: (formData.value.recommendNew = false)
checkboxGroup.value.includes('recommendGood')
? (formData.value.recommendGood = true)
: (formData.value.recommendGood = false)
}
const OtherSettingsFormRef = ref() // 表单Ref
// 表单数据 // 表单数据
const formData = ref<SpuType>({ const formData = ref<SpuType>({
sort: 1, // 商品排序 sort: 1, // 商品排序
...@@ -110,6 +85,21 @@ const rules = reactive({ ...@@ -110,6 +85,21 @@ const rules = reactive({
giveIntegral: [required], giveIntegral: [required],
virtualSalesCount: [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 * 将传进来的值赋值给formData
...@@ -117,18 +107,17 @@ const rules = reactive({ ...@@ -117,18 +107,17 @@ const rules = reactive({
watch( watch(
() => props.propFormData, () => props.propFormData,
(data) => { (data) => {
if (!data) return if (!data) {
return
}
copyValueToTarget(formData.value, data) copyValueToTarget(formData.value, data)
// TODO 如果先修改其他设置的值,再改变商品详情或是商品信息会重置其他设置页面中的相关值 下一个版本修复 recommendOptions.forEach(({ value }) => {
checkboxGroup.value = [] if (formData.value[value] && !checkboxGroup.value.includes(value)) {
formData.value.recommendHot ? checkboxGroup.value.push('recommendHot') : '' checkboxGroup.value.push(value)
formData.value.recommendBenefit ? checkboxGroup.value.push('recommendBenefit') : '' }
formData.value.recommendBest ? checkboxGroup.value.push('recommendBest') : '' })
formData.value.recommendNew ? checkboxGroup.value.push('recommendNew') : ''
formData.value.recommendGood ? checkboxGroup.value.push('recommendGood') : ''
}, },
{ {
deep: true,
immediate: true immediate: true
} }
) )
...@@ -139,8 +128,8 @@ watch( ...@@ -139,8 +128,8 @@ watch(
const emit = defineEmits(['update:activeName']) const emit = defineEmits(['update:activeName'])
const validate = async () => { const validate = async () => {
// 校验表单 // 校验表单
if (!OtherSettingsFormRef) return if (!otherSettingsFormRef) return
return await unref(OtherSettingsFormRef).validate((valid) => { return await unref(otherSettingsFormRef).validate((valid) => {
if (!valid) { if (!valid) {
message.warning('商品其他设置未完善!!') message.warning('商品其他设置未完善!!')
emit('update:activeName', 'otherSettings') emit('update:activeName', 'otherSettings')
......
...@@ -2,23 +2,25 @@ ...@@ -2,23 +2,25 @@
<el-col v-for="(item, index) in attributeList" :key="index"> <el-col v-for="(item, index) in attributeList" :key="index">
<div> <div>
<el-text class="mx-1">属性名:</el-text> <el-text class="mx-1">属性名:</el-text>
<el-text class="mx-1">{{ item.name }}</el-text> <el-tag class="mx-1" closable type="success" @close="handleCloseProperty(index)"
>{{ item.name }}
</el-tag>
</div> </div>
<div> <div>
<el-text class="mx-1">属性值:</el-text> <el-text class="mx-1">属性值:</el-text>
<el-tag <el-tag
v-for="(value, valueIndex) in item.values" v-for="(value, valueIndex) in item.values"
:key="value.id" :key="value.id"
:disable-transitions="false"
class="mx-1" class="mx-1"
closable closable
@close="handleClose(index, valueIndex)" @close="handleCloseValue(index, valueIndex)"
> >
{{ value.name }} {{ value.name }}
</el-tag> </el-tag>
<el-input <el-input
v-show="inputVisible(index)" v-show="inputVisible(index)"
ref="InputRef" :id="`input${index}`"
:ref="setInputRef"
v-model="inputValue" v-model="inputValue"
class="!w-20" class="!w-20"
size="small" size="small"
...@@ -51,17 +53,25 @@ const inputVisible = computed(() => (index) => { ...@@ -51,17 +53,25 @@ const inputVisible = computed(() => (index) => {
if (attributeIndex.value === null) return false if (attributeIndex.value === null) return false
if (attributeIndex.value === index) return true if (attributeIndex.value === index) return true
}) })
const InputRef = ref() //标签输入框Ref 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 attributeList = ref([]) // 商品属性列表
const props = defineProps({ const props = defineProps({
attributeData: { propertyList: {
type: Array, type: Array,
default: () => {} default: () => {}
} }
}) })
watch( watch(
() => props.attributeData, () => props.propertyList,
(data) => { (data) => {
if (!data) return if (!data) return
attributeList.value = data attributeList.value = data
...@@ -72,18 +82,22 @@ watch( ...@@ -72,18 +82,22 @@ watch(
} }
) )
/** 删除标签 tagValue 标签值*/ /** 删除属性值*/
const handleClose = (index, valueIndex) => { const handleCloseValue = (index, valueIndex) => {
attributeList.value[index].values?.splice(valueIndex, 1) attributeList.value[index].values?.splice(valueIndex, 1)
} }
/** 删除属性*/
const handleCloseProperty = (index) => {
attributeList.value?.splice(index, 1)
}
/** 显示输入框并获取焦点 */ /** 显示输入框并获取焦点 */
const showInput = async (index) => { const showInput = async (index) => {
attributeIndex.value = index attributeIndex.value = index
// 因为组件在ref中所以需要用索引获取对应的Ref inputRef.value[index].focus()
InputRef.value[index]!.input!.focus()
} }
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
/** 输入框失去焦点或点击回车时触发 */ /** 输入框失去焦点或点击回车时触发 */
const handleInputConfirm = async (index, propertyId) => { const handleInputConfirm = async (index, propertyId) => {
if (inputValue.value) { if (inputValue.value) {
...@@ -92,6 +106,7 @@ const handleInputConfirm = async (index, propertyId) => { ...@@ -92,6 +106,7 @@ const handleInputConfirm = async (index, propertyId) => {
const id = await PropertyApi.createPropertyValue({ propertyId, name: inputValue.value }) const id = await PropertyApi.createPropertyValue({ propertyId, name: inputValue.value })
attributeList.value[index].values.push({ id, name: inputValue.value }) attributeList.value[index].values.push({ id, name: inputValue.value })
message.success(t('common.createSuccess')) message.success(t('common.createSuccess'))
emit('success', attributeList.value)
} catch { } catch {
message.error('添加失败,请重试') // TODO 缺少国际化 message.error('添加失败,请重试') // TODO 缺少国际化
} }
......
...@@ -7,12 +7,9 @@ ...@@ -7,12 +7,9 @@
:rules="formRules" :rules="formRules"
label-width="80px" label-width="80px"
> >
<el-form-item label="名称" prop="name"> <el-form-item label="属性名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" /> <el-input v-model="formData.name" placeholder="请输入名称" />
</el-form-item> </el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" />
</el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
...@@ -30,14 +27,31 @@ const dialogVisible = ref(false) // 弹窗的是否展示 ...@@ -30,14 +27,31 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('添加商品属性') // 弹窗的标题 const dialogTitle = ref('添加商品属性') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formData = ref({ const formData = ref({
name: '', name: ''
remark: ''
}) })
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }] name: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
}) })
const formRef = ref() // 表单 Ref 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 () => { const open = async () => {
dialogVisible.value = true dialogVisible.value = true
...@@ -46,7 +60,6 @@ const open = async () => { ...@@ -46,7 +60,6 @@ const open = async () => {
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => { const submitForm = async () => {
// 校验表单 // 校验表单
if (!formRef) return if (!formRef) return
...@@ -60,12 +73,12 @@ const submitForm = async () => { ...@@ -60,12 +73,12 @@ const submitForm = async () => {
const res = await PropertyApi.getPropertyListAndValue({ name: data.name }) const res = await PropertyApi.getPropertyListAndValue({ name: data.name })
if (res.length === 0) { if (res.length === 0) {
const propertyId = await PropertyApi.createProperty(data) const propertyId = await PropertyApi.createProperty(data)
emit('success', { id: propertyId, ...formData.value, values: [] }) attributeList.value.push({ id: propertyId, ...formData.value, values: [] })
} else { } else {
if (res[0].values === null) { if (res[0].values === null) {
res[0].values = [] res[0].values = []
} }
emit('success', res[0]) // 因为只用一个 attributeList.value.push(res[0]) // 因为只用一个
} }
message.success(t('common.createSuccess')) message.success(t('common.createSuccess'))
dialogVisible.value = false dialogVisible.value = false
......
<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>
<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>
<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>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment