commit
f908c12963
6
.env.dev
6
.env.dev
|
@ -1,5 +1,5 @@
|
|||
# 开发环境
|
||||
NODE_ENV=production
|
||||
NODE_ENV=development
|
||||
|
||||
VITE_DEV=false
|
||||
|
||||
|
@ -19,13 +19,13 @@ VITE_API_URL=/admin-api
|
|||
VITE_BASE_PATH=/
|
||||
|
||||
# 是否删除debugger
|
||||
VITE_DROP_DEBUGGER=false
|
||||
VITE_DROP_DEBUGGER=true
|
||||
|
||||
# 是否删除console.log
|
||||
VITE_DROP_CONSOLE=false
|
||||
|
||||
# 是否sourcemap
|
||||
VITE_SOURCEMAP=true
|
||||
VITE_SOURCEMAP=false
|
||||
|
||||
# 输出路径
|
||||
VITE_OUT_DIR=dist-dev
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
"url": "^0.11.1",
|
||||
"video.js": "^8.3.0",
|
||||
"vue": "3.3.4",
|
||||
"vue-dompurify-html": "^4.1.4",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-router": "^4.2.4",
|
||||
"vue-types": "^5.1.0",
|
||||
|
|
|
@ -7,8 +7,7 @@ export interface Property {
|
|||
valueName?: string // 属性值名称
|
||||
}
|
||||
|
||||
// TODO puhui999:是不是直接叫 Sku 更简洁一点哈。type 待后面,总感觉有个类型?
|
||||
export interface SkuType {
|
||||
export interface Sku {
|
||||
id?: number // 商品 SKU 编号
|
||||
spuId?: number // SPU 编号
|
||||
properties?: Property[] // 属性数组
|
||||
|
@ -25,8 +24,7 @@ export interface SkuType {
|
|||
salesCount?: number // 商品销量
|
||||
}
|
||||
|
||||
// TODO puhui999:是不是直接叫 Spu 更简洁一点哈。type 待后面,总感觉有个类型?
|
||||
export interface SpuType {
|
||||
export interface Spu {
|
||||
id?: number
|
||||
name?: string // 商品名称
|
||||
categoryId?: number | null // 商品分类
|
||||
|
@ -39,9 +37,9 @@ export interface SpuType {
|
|||
brandId?: number | null // 商品品牌编号
|
||||
specType?: boolean // 商品规格
|
||||
subCommissionType?: boolean // 分销类型
|
||||
skus: SkuType[] // sku数组
|
||||
skus?: Sku[] // sku数组
|
||||
description?: string // 商品详情
|
||||
sort?: string // 商品排序
|
||||
sort?: number // 商品排序
|
||||
giveIntegral?: number // 赠送积分
|
||||
virtualSalesCount?: number // 虚拟销量
|
||||
recommendHot?: boolean // 是否热卖
|
||||
|
@ -49,6 +47,13 @@ export interface SpuType {
|
|||
recommendBest?: boolean // 是否精品
|
||||
recommendNew?: boolean // 是否新品
|
||||
recommendGood?: boolean // 是否优品
|
||||
price?: number // 商品价格
|
||||
salesCount?: number // 商品销量
|
||||
marketPrice?: number // 市场价
|
||||
costPrice?: number // 成本价
|
||||
stock?: number // 商品库存
|
||||
createTime?: Date // 商品创建时间
|
||||
status?: number // 商品状态
|
||||
}
|
||||
|
||||
// 获得 Spu 列表
|
||||
|
@ -62,12 +67,12 @@ export const getTabsCount = () => {
|
|||
}
|
||||
|
||||
// 创建商品 Spu
|
||||
export const createSpu = (data: SpuType) => {
|
||||
export const createSpu = (data: Spu) => {
|
||||
return request.post({ url: '/product/spu/create', data })
|
||||
}
|
||||
|
||||
// 更新商品 Spu
|
||||
export const updateSpu = (data: SpuType) => {
|
||||
export const updateSpu = (data: Spu) => {
|
||||
return request.put({ url: '/product/spu/update', data })
|
||||
}
|
||||
|
||||
|
@ -90,3 +95,8 @@ export const deleteSpu = (id: number) => {
|
|||
export const exportSpu = async (params) => {
|
||||
return await request.download({ url: '/product/spu/export', params })
|
||||
}
|
||||
|
||||
// 获得商品 SPU 精简列表
|
||||
export const getSpuSimpleList = async () => {
|
||||
return request.get({ url: '/product/spu/get-simple-list' })
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// TODO @dhb52:vo 缺少
|
||||
|
||||
// 删除优惠劵
|
||||
export const deleteCoupon = async (id: number) => {
|
||||
return request.delete({
|
||||
url: `/promotion/coupon/delete?id=${id}`
|
||||
})
|
||||
}
|
||||
|
||||
// 获得优惠劵分页
|
||||
export const getCouponPage = async (params: PageParam) => {
|
||||
return request.get({
|
||||
url: '/promotion/coupon/page',
|
||||
params: params
|
||||
})
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
export interface CouponTemplateVO {
|
||||
id: number
|
||||
name: string
|
||||
status: number
|
||||
totalCount: number
|
||||
takeLimitCount: number
|
||||
takeType: number
|
||||
usePrice: number
|
||||
productScope: number
|
||||
productSpuIds: string
|
||||
validityType: number
|
||||
validStartTime: Date
|
||||
validEndTime: Date
|
||||
fixedStartTerm: number
|
||||
fixedEndTerm: number
|
||||
discountType: number
|
||||
discountPercent: number
|
||||
discountPrice: number
|
||||
discountLimitPrice: number
|
||||
takeCount: number
|
||||
useCount: number
|
||||
}
|
||||
|
||||
// 创建优惠劵模板
|
||||
export function createCouponTemplate(data: CouponTemplateVO) {
|
||||
return request.post({
|
||||
url: '/promotion/coupon-template/create',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 更新优惠劵模板
|
||||
export function updateCouponTemplate(data: CouponTemplateVO) {
|
||||
return request.put({
|
||||
url: '/promotion/coupon-template/update',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 更新优惠劵模板的状态
|
||||
export function updateCouponTemplateStatus(id: number, status: [0, 1]) {
|
||||
const data = {
|
||||
id,
|
||||
status
|
||||
}
|
||||
return request.put({
|
||||
url: '/promotion/coupon-template/update-status',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除优惠劵模板
|
||||
export function deleteCouponTemplate(id: number) {
|
||||
return request.delete({
|
||||
url: '/promotion/coupon-template/delete?id=' + id
|
||||
})
|
||||
}
|
||||
|
||||
// 获得优惠劵模板
|
||||
export function getCouponTemplate(id: number) {
|
||||
return request.get({
|
||||
url: '/promotion/coupon-template/get?id=' + id
|
||||
})
|
||||
}
|
||||
|
||||
// 获得优惠劵模板分页
|
||||
export function getCouponTemplatePage(params: PageParam) {
|
||||
return request.get({
|
||||
url: '/promotion/coupon-template/page',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
// 导出优惠劵模板 Excel
|
||||
export function exportCouponTemplateExcel(params: PageParam) {
|
||||
return request.get({
|
||||
url: '/promotion/coupon-template/export-excel',
|
||||
params: params,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import request from '@/config/axios'
|
||||
import { Sku, Spu } from '@/api/mall/product/spu'
|
||||
|
||||
export interface SeckillActivityVO {
|
||||
id: number
|
||||
spuIds: number[]
|
||||
name: string
|
||||
status: number
|
||||
remark: string
|
||||
startTime: Date
|
||||
endTime: Date
|
||||
sort: number
|
||||
configIds: string
|
||||
orderCount: number
|
||||
userCount: number
|
||||
totalPrice: number
|
||||
totalLimitCount: number
|
||||
singleLimitCount: number
|
||||
stock: number
|
||||
totalStock: number
|
||||
products: SeckillProductVO[]
|
||||
}
|
||||
|
||||
// 秒杀活动所需属性
|
||||
export interface SeckillProductVO {
|
||||
spuId: number
|
||||
skuId: number
|
||||
seckillPrice: number
|
||||
stock: number
|
||||
}
|
||||
|
||||
// 扩展 Sku 配置
|
||||
type SkuExtension = Sku & {
|
||||
productConfig: SeckillProductVO
|
||||
}
|
||||
|
||||
export interface SpuExtension extends Spu {
|
||||
skus: SkuExtension[] // 重写类型
|
||||
}
|
||||
|
||||
// 查询秒杀活动列表
|
||||
export const getSeckillActivityPage = async (params) => {
|
||||
return await request.get({ url: '/promotion/seckill-activity/page', params })
|
||||
}
|
||||
|
||||
// 查询秒杀活动详情
|
||||
export const getSeckillActivity = async (id: number) => {
|
||||
return await request.get({ url: '/promotion/seckill-activity/get?id=' + id })
|
||||
}
|
||||
|
||||
// 新增秒杀活动
|
||||
export const createSeckillActivity = async (data: SeckillActivityVO) => {
|
||||
return await request.post({ url: '/promotion/seckill-activity/create', data })
|
||||
}
|
||||
|
||||
// 修改秒杀活动
|
||||
export const updateSeckillActivity = async (data: SeckillActivityVO) => {
|
||||
return await request.put({ url: '/promotion/seckill-activity/update', data })
|
||||
}
|
||||
|
||||
// 删除秒杀活动
|
||||
export const deleteSeckillActivity = async (id: number) => {
|
||||
return await request.delete({ url: '/promotion/seckill-activity/delete?id=' + id })
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
export interface SeckillConfigVO {
|
||||
id: number
|
||||
name: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
picUrl: string
|
||||
status: number
|
||||
}
|
||||
|
||||
// 查询秒杀时段配置列表
|
||||
export const getSeckillConfigPage = async (params) => {
|
||||
return await request.get({ url: '/promotion/seckill-config/page', params })
|
||||
}
|
||||
|
||||
// 查询秒杀时段配置详情
|
||||
export const getSeckillConfig = async (id: number) => {
|
||||
return await request.get({ url: '/promotion/seckill-config/get?id=' + id })
|
||||
}
|
||||
|
||||
// 获得所有开启状态的秒杀时段精简列表
|
||||
export const getListAllSimple = async () => {
|
||||
return await request.get({ url: '/promotion/seckill-config/list-all-simple' })
|
||||
}
|
||||
|
||||
// 新增秒杀时段配置
|
||||
export const createSeckillConfig = async (data: SeckillConfigVO) => {
|
||||
return await request.post({ url: '/promotion/seckill-config/create', data })
|
||||
}
|
||||
|
||||
// 修改秒杀时段配置
|
||||
export const updateSeckillConfig = async (data: SeckillConfigVO) => {
|
||||
return await request.put({ url: '/promotion/seckill-config/update', data })
|
||||
}
|
||||
|
||||
// 修改时段配置状态
|
||||
export const updateSeckillConfigStatus = (id: number, status: number) => {
|
||||
const data = {
|
||||
id,
|
||||
status
|
||||
}
|
||||
return request.put({ url: '/promotion/seckill-config/update-status', data: data })
|
||||
}
|
||||
|
||||
// 删除秒杀时段配置
|
||||
export const deleteSeckillConfig = async (id: number) => {
|
||||
return await request.delete({ url: '/promotion/seckill-config/delete?id=' + id })
|
||||
}
|
|
@ -33,6 +33,11 @@ export const getDeliveryExpressTemplate = async (id: number) => {
|
|||
return await request.get({ url: '/trade/delivery/express-template/get?id=' + id })
|
||||
}
|
||||
|
||||
// 查询快递运费模板详情
|
||||
export const getSimpleTemplateList = async () => {
|
||||
return await request.get({ url: '/trade/delivery/express-template/list-all-simple' })
|
||||
}
|
||||
|
||||
// 新增快递运费模板
|
||||
export const createDeliveryExpressTemplate = async (data: DeliveryExpressTemplateVO) => {
|
||||
return await request.post({ url: '/trade/delivery/express-template/create', data })
|
||||
|
@ -47,8 +52,3 @@ export const updateDeliveryExpressTemplate = async (data: DeliveryExpressTemplat
|
|||
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 })
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
export interface DeliveryPickUpStoreVO {
|
||||
id: number
|
||||
name: string
|
||||
introduction: string
|
||||
phone: string
|
||||
areaId: number
|
||||
detailAddress: string
|
||||
logo: string
|
||||
openingTime: string
|
||||
closingTime: string
|
||||
latitude: number
|
||||
longitude: number
|
||||
status: number
|
||||
}
|
||||
|
||||
// 查询自提门店列表
|
||||
export const getDeliveryPickUpStorePage = async (params: DeliveryPickUpStorePageReqVO) => {
|
||||
return await request.get({ url: '/trade/delivery/pick-up-store/page', params })
|
||||
}
|
||||
|
||||
// 查询自提门店详情
|
||||
export const getDeliveryPickUpStore = async (id: number) => {
|
||||
return await request.get({ url: '/trade/delivery/pick-up-store/get?id=' + id })
|
||||
}
|
||||
|
||||
// 新增自提门店
|
||||
export const createDeliveryPickUpStore = async (data: DeliveryPickUpStoreVO) => {
|
||||
return await request.post({ url: '/trade/delivery/pick-up-store/create', data })
|
||||
}
|
||||
|
||||
// 修改自提门店
|
||||
export const updateDeliveryPickUpStore = async (data: DeliveryPickUpStoreVO) => {
|
||||
return await request.put({ url: '/trade/delivery/pick-up-store/update', data })
|
||||
}
|
||||
|
||||
// 删除自提门店
|
||||
export const deleteDeliveryPickUpStore = async (id: number) => {
|
||||
return await request.delete({ url: '/trade/delivery/pick-up-store/delete?id=' + id })
|
||||
}
|
||||
|
||||
// 导出自提门店 Excel
|
||||
export const exportDeliveryPickUpStoreApi = async (params) => {
|
||||
return await request.download({ url: '/trade/delivery/pick-up-store/export-excel', params })
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// 获得交易订单分页
|
||||
// TODO @xiaobai:改成 getOrderPage
|
||||
export const getOrderList = (params: PageParam) => {
|
||||
return request.get({ url: '/trade/order/page', params })
|
||||
}
|
||||
|
||||
// 获得交易订单详情
|
||||
export const getOrderDetail = (id: number) => {
|
||||
return request.get({ url: '/trade/order/get-detail?id=' + id })
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
// TODO @xiaobai:这个放到 order/index.ts 里哈
|
||||
// TODO @xiaobai:注释放到变量后面,这样简洁一点
|
||||
// TODO @xiaobai:这个改成 TradeOrderRespVO
|
||||
export interface TradeOrderPageItemRespVO {
|
||||
// 订单编号
|
||||
id?: number
|
||||
// 订单流水号
|
||||
no?: string
|
||||
// 下单时间
|
||||
createTime?: Date
|
||||
// 订单类型
|
||||
type?: number
|
||||
// 订单来源
|
||||
terminal?: number
|
||||
// 用户编号
|
||||
userId?: number
|
||||
// 用户 IP
|
||||
userIp?: string
|
||||
// 用户备注
|
||||
userRemark?: string
|
||||
// 订单状态
|
||||
status?: number
|
||||
// 购买的商品数量
|
||||
productCount?: number
|
||||
// 订单完成时间
|
||||
finishTime?: Date
|
||||
// 订单取消时间
|
||||
cancelTime?: Date
|
||||
// 取消类型
|
||||
cancelType?: number
|
||||
// 商家备注
|
||||
remark?: string
|
||||
// 支付订单编号
|
||||
payOrderId: number
|
||||
// 是否已支付
|
||||
payed?: boolean
|
||||
// 付款时间
|
||||
payTime?: Date
|
||||
// 支付渠道
|
||||
payChannelCode?: string
|
||||
// 商品原价(总)
|
||||
originalPrice?: number
|
||||
// 订单原价(总)
|
||||
orderPrice?: number
|
||||
// 订单优惠(总)
|
||||
discountPrice?: number
|
||||
// 运费金额
|
||||
deliveryPrice?: number
|
||||
// 订单调价(总)
|
||||
adjustPrice?: number
|
||||
// 应付金额(总)
|
||||
payPrice?: number
|
||||
// 配送模板编号
|
||||
deliveryTemplateId?: number
|
||||
// 发货物流公司编号
|
||||
logisticsId?: number
|
||||
// 发货物流单号
|
||||
logisticsNo?: string
|
||||
// 发货状态
|
||||
deliveryStatus?: number
|
||||
// 发货时间
|
||||
deliveryTime?: Date
|
||||
// 收货时间
|
||||
receiveTime?: Date
|
||||
// 收件人名称
|
||||
receiverName?: string
|
||||
// 收件人手机
|
||||
receiverMobile?: string
|
||||
// 收件人地区编号
|
||||
receiverAreaId?: number
|
||||
// 收件人邮编
|
||||
receiverPostCode?: number
|
||||
// 收件人详细地址
|
||||
receiverDetailAddress?: string
|
||||
// 售后状态
|
||||
afterSaleStatus?: number
|
||||
// 退款金额
|
||||
refundPrice?: number
|
||||
// 优惠劵编号
|
||||
couponId?: number
|
||||
// 优惠劵减免金额
|
||||
couponPrice?: number
|
||||
// 积分抵扣的金额
|
||||
pointPrice?: number
|
||||
//收件人地区名字
|
||||
receiverAreaName?: string
|
||||
// 订单项列表
|
||||
items?: TradeOrderItemBaseVO[]
|
||||
//用户信息
|
||||
user?: MemberUserRespDTO
|
||||
}
|
||||
|
||||
// TODO @xiaobai:这个改成 TradeOrderItemRespVO
|
||||
/**
|
||||
* 交易订单项 Base VO,提供给添加、修改、详细的子 VO 使用
|
||||
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
|
||||
*/
|
||||
export interface TradeOrderItemBaseVO {
|
||||
// ========== 订单项基本信息 ==========
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
id?: number
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
userId?: number
|
||||
/**
|
||||
* 订单编号
|
||||
*/
|
||||
orderId?: number
|
||||
// ========== 商品基本信息 ==========
|
||||
/**
|
||||
* 商品 SPU 编号
|
||||
*/
|
||||
spuId?: number
|
||||
/**
|
||||
* 商品 SPU 名称
|
||||
*/
|
||||
spuName?: string
|
||||
/**
|
||||
* 商品 SKU 编号
|
||||
*/
|
||||
skuId?: number
|
||||
/**
|
||||
* 商品图片
|
||||
*/
|
||||
picUrl?: string
|
||||
/**
|
||||
* 购买数量
|
||||
*/
|
||||
count?: number
|
||||
// ========== 价格 + 支付基本信息 ==========
|
||||
/**
|
||||
* 商品原价(总)
|
||||
*/
|
||||
originalPrice?: number
|
||||
/**
|
||||
* 商品原价(单)
|
||||
*/
|
||||
originalUnitPrice?: number
|
||||
/**
|
||||
* 商品优惠(总)
|
||||
*/
|
||||
discountPrice?: number
|
||||
/**
|
||||
* 商品实付金额(总)
|
||||
*/
|
||||
payPrice?: number
|
||||
/**
|
||||
* 子订单分摊金额(总)
|
||||
*/
|
||||
orderPartPrice?: number
|
||||
/**
|
||||
* 分摊后子订单实付金额(总)
|
||||
*/
|
||||
orderDividePrice?: number
|
||||
// ========== 营销基本信息 ==========
|
||||
// TODO 芋艿:在捉摸一下
|
||||
// ========== 售后基本信息 ==========
|
||||
/**
|
||||
* 售后状态
|
||||
*/
|
||||
afterSaleStatus?: number
|
||||
//属性数组
|
||||
properties?: ProductPropertyValueDetailRespVO[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理后台 - 商品属性值的明细 Response VO
|
||||
*/
|
||||
export interface ProductPropertyValueDetailRespVO {
|
||||
/**
|
||||
* 属性的编号
|
||||
*/
|
||||
propertyId?: number
|
||||
/**
|
||||
* 属性的名称
|
||||
*/
|
||||
propertyName?: string
|
||||
/**
|
||||
* 属性值的编号
|
||||
*/
|
||||
valueId?: number
|
||||
/**
|
||||
* 属性值的名称
|
||||
*/
|
||||
valueName?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单详情查询 请求
|
||||
*/
|
||||
export interface TradeOrderPageReqVO {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
no?: string
|
||||
userId?: string
|
||||
userNickname?: string
|
||||
userMobile?: string
|
||||
receiverName?: string
|
||||
receiverMobile?: string
|
||||
terminal?: string
|
||||
type?: number
|
||||
status?: number
|
||||
payChannelCode?: string
|
||||
createTime?: [Date, Date]
|
||||
spuName?: string
|
||||
itemCount?: string
|
||||
all?: string
|
||||
}
|
||||
|
||||
//用户信息
|
||||
export interface MemberUserRespDTO {
|
||||
id?: number
|
||||
nickname?: string
|
||||
status?: number
|
||||
avatar?: string
|
||||
mobile?: string
|
||||
}
|
||||
//订单详情选中type
|
||||
export interface SelectType {
|
||||
queryParams: TradeOrderPageReqVO
|
||||
selectTotal: number //选中的数量
|
||||
selectAllFlag: boolean //全选标识
|
||||
selectData: Map<number, Set<string>> //存放涉及选中得页面以及每页选中得数据订单号 全选时根据条件查询 排除取消的list订单
|
||||
unSelectList: Set<string> //登记取消的list 全选标识为true 时登记单独取消的list,再次选中时排除, 全选标识为false 时清空list
|
||||
}
|
|
@ -8,32 +8,12 @@ export interface ConfigVO {
|
|||
tradeGivePoint: number
|
||||
}
|
||||
|
||||
// 查询积分设置列表
|
||||
export const getConfigPage = async (params) => {
|
||||
return await request.get({ url: `/point/config/page`, params })
|
||||
}
|
||||
|
||||
// 查询积分设置详情
|
||||
export const getConfig = async (id: number) => {
|
||||
return await request.get({ url: `/point/config/get?id=` + id })
|
||||
export const getConfig = async () => {
|
||||
return await request.get({ url: `/point/config/get` })
|
||||
}
|
||||
|
||||
// 新增积分设置
|
||||
export const createConfig = async (data: ConfigVO) => {
|
||||
return await request.post({ url: `/point/config/create`, data })
|
||||
}
|
||||
|
||||
// 修改积分设置
|
||||
export const updateConfig = async (data: ConfigVO) => {
|
||||
return await request.put({ url: `/point/config/update`, data })
|
||||
}
|
||||
|
||||
// 删除积分设置
|
||||
export const deleteConfig = async (id: number) => {
|
||||
return await request.delete({ url: `/point/config/delete?id=` + id })
|
||||
}
|
||||
|
||||
// 导出积分设置 Excel
|
||||
export const exportConfig = async (params) => {
|
||||
return await request.download({ url: `/point/config/export-excel`, params })
|
||||
// 新增修改积分设置
|
||||
export const saveConfig = async (data: ConfigVO) => {
|
||||
return await request.put({ url: `/point/config/save`, data })
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ const props = defineProps({
|
|||
})
|
||||
|
||||
const getBindValue = computed(() => {
|
||||
const delArr: string[] = ['fullscreen', 'title', 'maxHeight']
|
||||
const delArr: string[] = ['fullscreen', 'title', 'maxHeight', 'appendToBody']
|
||||
const attrs = useAttrs()
|
||||
const obj = { ...attrs, ...props }
|
||||
for (const key in obj) {
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
<script lang="tsx">
|
||||
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
|
||||
import { ElForm, ElFormItem, ElRow, ElCol, ElTooltip } from 'element-plus'
|
||||
import { computed, defineComponent, onMounted, PropType, ref, unref, watch } from 'vue'
|
||||
import { ElCol, ElForm, ElFormItem, ElRow, ElTooltip } from 'element-plus'
|
||||
import { componentMap } from './componentMap'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import {
|
||||
setTextPlaceholder,
|
||||
setGridProp,
|
||||
setComponentProps,
|
||||
setItemComponentSlots,
|
||||
initModel,
|
||||
setFormItemSlots
|
||||
setComponentProps,
|
||||
setFormItemSlots,
|
||||
setGridProp,
|
||||
setItemComponentSlots,
|
||||
setTextPlaceholder
|
||||
} from './helper'
|
||||
import { useRenderSelect } from './components/useRenderSelect'
|
||||
import { useRenderRadio } from './components/useRenderRadio'
|
||||
|
@ -197,7 +197,7 @@ export default defineComponent({
|
|||
<span>{item.label}</span>
|
||||
<ElTooltip placement="right" raw-content>
|
||||
{{
|
||||
content: () => <span v-html={item.labelMessage}></span>,
|
||||
content: () => <span v-dompurify-html={item.labelMessage}></span>,
|
||||
default: () => (
|
||||
<Icon
|
||||
icon="ep:warning"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { reactive } from 'vue'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import { findIndex } from '@/utils'
|
||||
import { eachTree, treeMap, filter } from '@/utils/tree'
|
||||
import { eachTree, filter, treeMap } from '@/utils/tree'
|
||||
import { getBoolDictOptions, getDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||
|
||||
import { FormSchema } from '@/types/form'
|
||||
|
@ -36,8 +36,11 @@ type CrudSearchParams = {
|
|||
type CrudTableParams = {
|
||||
// 是否显示表头
|
||||
show?: boolean
|
||||
// 列宽配置
|
||||
width?: number | string
|
||||
// 列是否固定在左侧或者右侧
|
||||
fixed?: 'left' | 'right'
|
||||
} & Omit<FormSchema, 'field'>
|
||||
|
||||
type CrudFormParams = {
|
||||
// 是否显示表单项
|
||||
show?: boolean
|
||||
|
|
|
@ -38,9 +38,10 @@ import App from './App.vue'
|
|||
import './permission'
|
||||
|
||||
import '@/plugins/tongji' // 百度统计
|
||||
|
||||
import Logger from '@/utils/Logger'
|
||||
|
||||
import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患
|
||||
|
||||
// 创建实例
|
||||
const setupAll = async () => {
|
||||
const app = createApp(App)
|
||||
|
@ -61,6 +62,8 @@ const setupAll = async () => {
|
|||
|
||||
await router.isReady()
|
||||
|
||||
app.use(VueDOMPurifyHTML)
|
||||
|
||||
app.mount('#app')
|
||||
}
|
||||
|
||||
|
|
|
@ -195,6 +195,22 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
noTagsView: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/trade/order',
|
||||
component: Layout,
|
||||
name: 'order',
|
||||
meta: {
|
||||
hidden: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'detail',
|
||||
name: 'TradeOrderDetail',
|
||||
component: () => import('@/views/mall/trade/order/tradeOrderDetail.vue'),
|
||||
meta: { title: '订单详情', hidden: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/403',
|
||||
component: () => import('@/views/Error/403.vue'),
|
||||
|
@ -355,7 +371,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
},
|
||||
children: [
|
||||
{
|
||||
path: 'productSpuAdd', // TODO @puhui999:最好拆成 add 和 edit 两个路由;添加商品;修改商品 fix
|
||||
path: 'spu/add',
|
||||
component: () => import('@/views/mall/product/spu/addForm.vue'),
|
||||
name: 'ProductSpuAdd',
|
||||
meta: {
|
||||
|
@ -368,9 +384,9 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
}
|
||||
},
|
||||
{
|
||||
path: 'productSpuEdit/:spuId(\\d+)',
|
||||
path: 'spu/edit/:spuId(\\d+)',
|
||||
component: () => import('@/views/mall/product/spu/addForm.vue'),
|
||||
name: 'productSpuEdit',
|
||||
name: 'ProductSpuEdit',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
|
@ -379,6 +395,19 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
title: '编辑商品',
|
||||
activeMenu: '/product/product-spu'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'spu/detail/:spuId(\\d+)',
|
||||
component: () => import('@/views/mall/product/spu/addForm.vue'),
|
||||
name: 'ProductSpuDetail',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true,
|
||||
icon: 'ep:view',
|
||||
title: '商品详情',
|
||||
activeMenu: '/product/product-spu'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
export type TableColumn = {
|
||||
field: string
|
||||
label?: string
|
||||
width?: number | string
|
||||
fixed?: 'left' | 'right'
|
||||
children?: TableColumn[]
|
||||
} & Recordable
|
||||
|
||||
|
|
|
@ -222,7 +222,7 @@ export const PayRefundStatusEnum = {
|
|||
}
|
||||
|
||||
/**
|
||||
* 商品SPU枚举类
|
||||
* 商品 SPU 状态
|
||||
*/
|
||||
export const ProductSpuStatusEnum = {
|
||||
RECYCLE: {
|
||||
|
@ -238,3 +238,59 @@ export const ProductSpuStatusEnum = {
|
|||
name: '上架'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优惠劵模板的有限期类型的枚举
|
||||
*/
|
||||
export const CouponTemplateValidityTypeEnum = {
|
||||
DATE: {
|
||||
type: 1,
|
||||
name: '固定日期可用'
|
||||
},
|
||||
TERM: {
|
||||
type: 2,
|
||||
name: '领取之后可用'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 营销的商品范围枚举
|
||||
*/
|
||||
export const PromotionProductScopeEnum = {
|
||||
ALL: {
|
||||
scope: 1,
|
||||
name: '全部商品参与'
|
||||
},
|
||||
SPU: {
|
||||
scope: 2,
|
||||
name: '指定商品参与'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 营销的条件类型枚举
|
||||
*/
|
||||
export const PromotionConditionTypeEnum = {
|
||||
PRICE: {
|
||||
type: 10,
|
||||
name: '满 N 元'
|
||||
},
|
||||
COUNT: {
|
||||
type: 20,
|
||||
name: '满 N 件'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优惠类型枚举
|
||||
*/
|
||||
export const PromotionDiscountTypeEnum = {
|
||||
PRICE: {
|
||||
type: 1,
|
||||
name: '满减'
|
||||
},
|
||||
PERCENT: {
|
||||
type: 2,
|
||||
name: '折扣'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ export const getIntDictOptions = (dictType: string) => {
|
|||
value: parseInt(dict.value + '')
|
||||
})
|
||||
})
|
||||
|
||||
return dictOption
|
||||
}
|
||||
|
||||
|
@ -146,12 +145,30 @@ export enum DICT_TYPE {
|
|||
MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型
|
||||
MP_MESSAGE_TYPE = 'mp_message_type', // 消息类型
|
||||
|
||||
// ========== MALL 模块 ==========
|
||||
// ========== MALL - 会员模块 ==========
|
||||
MEMBER_POINT_BIZ_TYPE = 'member_point_biz_type', // 积分的业务类型
|
||||
MEMBER_POINT_STATUS = 'member_point_status', // 积分的状态
|
||||
|
||||
// ========== MALL - 商品模块 ==========
|
||||
PRODUCT_UNIT = 'product_unit', // 商品单位
|
||||
PRODUCT_SPU_STATUS = 'product_spu_status', //商品状态
|
||||
// ========== MALL 交易模块 ==========
|
||||
|
||||
// ========== MALL - 交易模块 ==========
|
||||
EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode', //快递的计费方式
|
||||
//积分模块//
|
||||
POINT_BIZ_TYPE = 'point_biz_type',
|
||||
POINT_STATUS = 'point_status'
|
||||
TRADE_AFTER_SALE_STATUS = 'trade_after_sale_status', // 售后 - 状态
|
||||
TRADE_AFTER_SALE_WAY = 'trade_after_sale_way', // 售后 - 方式
|
||||
TRADE_AFTER_SALE_TYPE = 'trade_after_sale_type', // 售后 - 类型
|
||||
TRADE_ORDER_TYPE = 'trade_order_type', // 订单 - 类型
|
||||
TRADE_ORDER_STATUS = 'trade_order_status', // 订单 - 状态
|
||||
TRADE_ORDER_ITEM_AFTER_SALE_STATUS = 'trade_order_item_after_sale_status', // 订单项 - 售后状态
|
||||
TERMINAL = 'terminal', // 终端
|
||||
|
||||
// ========== MALL - 营销模块 ==========
|
||||
PROMOTION_DISCOUNT_TYPE = 'promotion_discount_type', // 优惠类型
|
||||
PROMOTION_PRODUCT_SCOPE = 'promotion_product_scope', // 营销的商品范围
|
||||
PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE = 'promotion_coupon_template_validity_type', // 优惠劵模板的有限期类型
|
||||
PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态
|
||||
PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式
|
||||
PROMOTION_ACTIVITY_STATUS = 'promotion_activity_status', // 优惠活动的状态
|
||||
PROMOTION_CONDITION_TYPE = 'promotion_condition_type' // 营销的条件类型枚举
|
||||
}
|
||||
|
|
|
@ -149,13 +149,28 @@ export const dateFormatter = (row, column, cellValue) => {
|
|||
return formatDate(cellValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* element plus 的时间 Formatter 实现,使用 YYYY-MM-DD 格式
|
||||
*
|
||||
* @param row 行数据
|
||||
* @param column 字段
|
||||
* @param cellValue 字段值
|
||||
*/
|
||||
// @ts-ignore
|
||||
export const dateFormatter2 = (row, column, cellValue) => {
|
||||
if (!cellValue) {
|
||||
return
|
||||
}
|
||||
return formatDate(cellValue, 'YYYY-MM-DD')
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置起始日期,时间为00:00:00
|
||||
* @param param 传入日期
|
||||
* @returns 带时间00:00:00的日期
|
||||
*/
|
||||
export function beginOfDay(param: Date) {
|
||||
return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 0, 0, 0, 0)
|
||||
return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 0, 0, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -164,7 +179,7 @@ export function beginOfDay(param: Date) {
|
|||
* @returns 带时间23:59:59的日期
|
||||
*/
|
||||
export function endOfDay(param: Date) {
|
||||
return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 23, 59, 59, 999)
|
||||
return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 23, 59, 59)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,6 +3,7 @@ interface TreeHelperConfig {
|
|||
children: string
|
||||
pid: string
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: TreeHelperConfig = {
|
||||
id: 'id',
|
||||
children: 'children',
|
||||
|
@ -133,6 +134,7 @@ export const filter = <T = any>(
|
|||
): T[] => {
|
||||
config = getConfig(config)
|
||||
const children = config.children as string
|
||||
|
||||
function listFilter(list: T[]) {
|
||||
return list
|
||||
.map((node: any) => ({ ...node }))
|
||||
|
@ -141,6 +143,7 @@ export const filter = <T = any>(
|
|||
return func(node) || (node[children] && node[children].length)
|
||||
})
|
||||
}
|
||||
|
||||
return listFilter(tree)
|
||||
}
|
||||
|
||||
|
@ -264,6 +267,7 @@ export const handleTree = (data: any[], id?: string, parentId?: string, children
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
|
@ -302,3 +306,94 @@ export const handleTree2 = (data, id, parentId, children, rootId) => {
|
|||
})
|
||||
return treeData !== '' ? treeData : data
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验选中的节点,是否为指定 level
|
||||
*
|
||||
* @param tree 要操作的树结构数据
|
||||
* @param nodeId 需要判断在什么层级的数据
|
||||
* @param level 检查的级别, 默认检查到二级
|
||||
* @return true 是;false 否
|
||||
*/
|
||||
export const checkSelectedNode = (tree: any[], nodeId: any, level = 2): boolean => {
|
||||
if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) {
|
||||
console.warn('tree must be an array')
|
||||
return false
|
||||
}
|
||||
|
||||
// 校验是否是一级节点
|
||||
if (tree.some((item) => item.id === nodeId)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 递归计数
|
||||
let count = 1
|
||||
|
||||
// 深层次校验
|
||||
function performAThoroughValidation(arr: any[]): boolean {
|
||||
count += 1
|
||||
for (const item of arr) {
|
||||
if (item.id === nodeId) {
|
||||
return true
|
||||
} else if (typeof item.children !== 'undefined' && item.children.length !== 0) {
|
||||
if (performAThoroughValidation(item.children)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for (const item of tree) {
|
||||
count = 1
|
||||
if (performAThoroughValidation(item.children)) {
|
||||
// 找到后对比是否是期望的层级
|
||||
if (count >= level) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点的完整结构
|
||||
* @param tree 树数据
|
||||
* @param nodeId 节点 id
|
||||
*/
|
||||
export const treeToString = (tree: any[], nodeId) => {
|
||||
if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) {
|
||||
console.warn('tree must be an array')
|
||||
return ''
|
||||
}
|
||||
// 校验是否是一级节点
|
||||
const node = tree.find((item) => item.id === nodeId)
|
||||
if (typeof node !== 'undefined') {
|
||||
return node.name
|
||||
}
|
||||
let str = ''
|
||||
|
||||
function performAThoroughValidation(arr) {
|
||||
for (const item of arr) {
|
||||
if (item.id === nodeId) {
|
||||
str += `/${item.name}`
|
||||
return true
|
||||
} else if (typeof item.children !== 'undefined' && item.children.length !== 0) {
|
||||
str += `/${item.name}`
|
||||
if (performAThoroughValidation(item.children)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for (const item of tree) {
|
||||
str = `${item.name}`
|
||||
if (performAThoroughValidation(item.children)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
|
|
@ -185,12 +185,17 @@ const signIn = async () => {
|
|||
await getTenantId()
|
||||
const data = await validForm()
|
||||
if (!data) return
|
||||
ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在加载系统中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
loginLoading.value = true
|
||||
smsVO.loginSms.mobile = loginData.loginForm.mobileNumber
|
||||
smsVO.loginSms.code = loginData.loginForm.code
|
||||
await smsLogin(smsVO.loginSms)
|
||||
.then(async (res) => {
|
||||
setToken(res?.token)
|
||||
setToken(res)
|
||||
if (!redirect.value) {
|
||||
redirect.value = '/'
|
||||
}
|
||||
|
@ -199,6 +204,10 @@ const signIn = async () => {
|
|||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loginLoading.value = false
|
||||
setTimeout(() => {
|
||||
const loadingInstance = ElLoading.service()
|
||||
loadingInstance.close()
|
||||
}, 400)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
</ContentWrap>
|
||||
|
||||
<!-- 弹窗:表单预览 -->
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" max-height="600">
|
||||
<div ref="editor" v-if="dialogVisible">
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" max-height="600">
|
||||
<div v-if="dialogVisible" ref="editor">
|
||||
<el-button style="float: right" @click="copy(formData)">
|
||||
{{ t('common.copy') }}
|
||||
</el-button>
|
||||
|
@ -30,6 +30,7 @@
|
|||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'InfraBuild' })
|
||||
import FcDesigner from '@form-create/designer'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { isString } from '@/utils/is'
|
||||
|
@ -40,8 +41,6 @@ import xml from 'highlight.js/lib/languages/java'
|
|||
import json from 'highlight.js/lib/languages/json'
|
||||
import formCreate from '@form-create/element-ui'
|
||||
|
||||
defineOptions({ name: 'InfraBuild' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
{{ t('common.copy') }}
|
||||
</el-button>
|
||||
<div>
|
||||
<pre><code class="hljs" v-html="highlightedCode(item)"></code></pre>
|
||||
<pre><code v-dompurify-html="highlightedCode(item)" class="hljs"></code></pre>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<BasicInfoForm
|
||||
ref="basicInfoRef"
|
||||
v-model:activeName="activeName"
|
||||
:is-detail="isDetail"
|
||||
:propFormData="formData"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
@ -12,6 +13,7 @@
|
|||
<DescriptionForm
|
||||
ref="descriptionRef"
|
||||
v-model:activeName="activeName"
|
||||
:is-detail="isDetail"
|
||||
:propFormData="formData"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
@ -19,13 +21,16 @@
|
|||
<OtherSettingsForm
|
||||
ref="otherSettingsRef"
|
||||
v-model:activeName="activeName"
|
||||
:is-detail="isDetail"
|
||||
: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 v-if="!isDetail" :loading="formLoading" type="primary" @click="submitForm">
|
||||
保存
|
||||
</el-button>
|
||||
<el-button @click="close">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
@ -44,16 +49,17 @@ defineOptions({ name: 'ProductSpuForm' })
|
|||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
const { params } = useRoute() // 查询参数
|
||||
const { params, name } = 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
|
||||
const isDetail = ref(false) // 是否查看详情
|
||||
const basicInfoRef = ref() // 商品信息Ref
|
||||
const descriptionRef = ref() // 商品详情Ref
|
||||
const otherSettingsRef = ref() // 其他设置Ref
|
||||
// spu 表单数据
|
||||
const formData = ref<ProductSpuApi.SpuType>({
|
||||
const formData = ref<ProductSpuApi.Spu>({
|
||||
name: '', // 商品名称
|
||||
categoryId: null, // 商品分类
|
||||
keyword: '', // 关键字
|
||||
|
@ -61,7 +67,7 @@ const formData = ref<ProductSpuApi.SpuType>({
|
|||
picUrl: '', // 商品封面图
|
||||
sliderPicUrls: [], // 商品轮播图
|
||||
introduction: '', // 商品简介
|
||||
deliveryTemplateId: 1, // 运费模版
|
||||
deliveryTemplateId: null, // 运费模版
|
||||
brandId: null, // 商品品牌
|
||||
specType: false, // 商品规格
|
||||
subCommissionType: false, // 分销类型
|
||||
|
@ -92,12 +98,15 @@ const formData = ref<ProductSpuApi.SpuType>({
|
|||
|
||||
/** 获得详情 */
|
||||
const getDetail = async () => {
|
||||
if ('ProductSpuDetail' === name) {
|
||||
isDetail.value = true
|
||||
}
|
||||
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) => {
|
||||
const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.Spu
|
||||
res.skus?.forEach((item) => {
|
||||
// 回显价格分转元
|
||||
item.price = formatToFraction(item.price)
|
||||
item.marketPrice = formatToFraction(item.marketPrice)
|
||||
|
@ -122,9 +131,10 @@ const submitForm = async () => {
|
|||
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) => {
|
||||
// 深拷贝一份, 这样最终 server 端不满足,不需要恢复,
|
||||
const deepCopyFormData = cloneDeep(unref(formData.value))
|
||||
// 兜底处理 sku 空数据
|
||||
formData.value.skus!.forEach((sku) => {
|
||||
// 因为是空数据这里判断一下商品条码是否为空就行
|
||||
if (sku.barCode === '') {
|
||||
const index = deepCopyFormData.skus.findIndex(
|
||||
|
@ -152,7 +162,7 @@ const submitForm = async () => {
|
|||
})
|
||||
deepCopyFormData.sliderPicUrls = newSliderPicUrls
|
||||
// 校验都通过后提交表单
|
||||
const data = deepCopyFormData as ProductSpuApi.SpuType
|
||||
const data = deepCopyFormData as ProductSpuApi.Spu
|
||||
const id = params.spuId as number
|
||||
if (!id) {
|
||||
await ProductSpuApi.createSpu(data)
|
||||
|
@ -172,7 +182,6 @@ const close = () => {
|
|||
delView(unref(currentRoute))
|
||||
push('/product/product-spu')
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await getDetail()
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
<template>
|
||||
<el-form ref="productSpuBasicInfoRef" :model="formData" :rules="rules" label-width="120px">
|
||||
<!-- 情况一:添加/修改 -->
|
||||
<el-form
|
||||
v-if="!isDetail"
|
||||
ref="productSpuBasicInfoRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="商品名称" prop="name">
|
||||
|
@ -7,7 +14,6 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<!-- TODO @puhui999:只能选根节点 -->
|
||||
<el-form-item label="商品分类" prop="categoryId">
|
||||
<el-tree-select
|
||||
v-model="formData.categoryId"
|
||||
|
@ -17,6 +23,7 @@
|
|||
class="w-1/1"
|
||||
node-key="id"
|
||||
placeholder="请选择商品分类"
|
||||
@change="categoryNodeClick"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
@ -60,9 +67,15 @@
|
|||
<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-option
|
||||
v-for="item in deliveryTemplateList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button class="ml-20px">运费模板</el-button>
|
||||
<!-- TODO 可能情况:善品录入后选择运费发现下拉选择中没有对应的模版 这里需不需要做添加运费模版后选择的功能 -->
|
||||
<!-- <el-button class="ml-20px">运费模板</el-button>-->
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
|
@ -95,6 +108,9 @@
|
|||
</el-col>
|
||||
<!-- 多规格添加-->
|
||||
<el-col :span="24">
|
||||
<el-form-item v-if="!formData.specType">
|
||||
<SkuList ref="skuListRef" :prop-form-data="formData" :propertyList="propertyList" />
|
||||
</el-form-item>
|
||||
<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" />
|
||||
|
@ -107,25 +123,82 @@
|
|||
<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" />
|
||||
|
||||
<!-- 情况二:详情 -->
|
||||
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
|
||||
<template #categoryId="{ row }"> {{ categoryString(row.categoryId) }}</template>
|
||||
<template #brandId="{ row }">
|
||||
{{ brandList.find((item) => item.id === row.brandId)?.name }}
|
||||
</template>
|
||||
<template #deliveryTemplateId="{ row }">
|
||||
{{ deliveryTemplateList.find((item) => item.id === row.deliveryTemplateId)?.name }}
|
||||
</template>
|
||||
<template #specType="{ row }">
|
||||
{{ row.specType ? '多规格' : '单规格' }}
|
||||
</template>
|
||||
<template #subCommissionType="{ row }">
|
||||
{{ row.subCommissionType ? '自行设置' : '默认设置' }}
|
||||
</template>
|
||||
<template #picUrl="{ row }">
|
||||
<el-image :src="row.picUrl" class="w-60px h-60px" @click="imagePreview(row.picUrl)" />
|
||||
</template>
|
||||
<template #sliderPicUrls="{ row }">
|
||||
<el-image
|
||||
v-for="(item, index) in row.sliderPicUrls"
|
||||
:key="index"
|
||||
:src="item.url"
|
||||
class="w-60px h-60px mr-10px"
|
||||
@click="imagePreview(row.sliderPicUrls)"
|
||||
/>
|
||||
</template>
|
||||
<template #skus>
|
||||
<SkuList
|
||||
ref="skuDetailListRef"
|
||||
:is-detail="isDetail"
|
||||
:prop-form-data="formData"
|
||||
:propertyList="propertyList"
|
||||
/>
|
||||
</template>
|
||||
</Descriptions>
|
||||
|
||||
<!-- 商品属性添加 Form 表单 -->
|
||||
<ProductPropertyAddForm ref="attributesAddFormRef" :propertyList="propertyList" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue'
|
||||
import { isArray } from '@/utils/is'
|
||||
import { copyValueToTarget } from '@/utils'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import { checkSelectedNode, defaultProps, handleTree, treeToString } from '@/utils/tree'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
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 { getPropertyList, ProductAttributes, ProductPropertyAddForm, SkuList } from './index'
|
||||
import { basicInfoSchema } from './spu.data'
|
||||
import type { Spu } from '@/api/mall/product/spu'
|
||||
import * as ProductCategoryApi from '@/api/mall/product/category'
|
||||
import { getSimpleBrandList } from '@/api/mall/product/brand'
|
||||
import { getSimpleTemplateList } from '@/api/mall/trade/delivery/expressTemplate/index'
|
||||
// ====== 商品详情相关操作 ======
|
||||
const { allSchemas } = useCrudSchemas(basicInfoSchema)
|
||||
/** 商品图预览 */
|
||||
const imagePreview = (args) => {
|
||||
const urlList = []
|
||||
if (isArray(args)) {
|
||||
args.forEach((item) => {
|
||||
urlList.push(item.url)
|
||||
})
|
||||
} else {
|
||||
urlList.push(args)
|
||||
}
|
||||
createImageViewer({
|
||||
urlList
|
||||
})
|
||||
}
|
||||
// ====== end ======
|
||||
|
||||
defineOptions({ name: 'ProductSpuBasicInfoForm' })
|
||||
|
||||
|
@ -133,10 +206,11 @@ const message = useMessage() // 消息弹窗
|
|||
|
||||
const props = defineProps({
|
||||
propFormData: {
|
||||
type: Object as PropType<SpuType>,
|
||||
type: Object as PropType<Spu>,
|
||||
default: () => {}
|
||||
},
|
||||
activeName: propTypes.string.def('')
|
||||
activeName: propTypes.string.def(''),
|
||||
isDetail: propTypes.bool.def(false) // 是否作为详情组件
|
||||
})
|
||||
const attributesAddFormRef = ref() // 添加商品属性表单
|
||||
const productSpuBasicInfoRef = ref() // 表单 Ref
|
||||
|
@ -146,15 +220,15 @@ const skuListRef = ref() // 商品属性列表Ref
|
|||
const generateSkus = (propertyList) => {
|
||||
skuListRef.value.generateTableData(propertyList)
|
||||
}
|
||||
const formData = reactive<SpuType>({
|
||||
const formData = reactive<Spu>({
|
||||
name: '', // 商品名称
|
||||
categoryId: null, // 商品分类
|
||||
keyword: '', // 关键字
|
||||
unit: '', // 单位
|
||||
unit: null, // 单位
|
||||
picUrl: '', // 商品封面图
|
||||
sliderPicUrls: [], // 商品轮播图
|
||||
introduction: '', // 商品简介
|
||||
deliveryTemplateId: 1, // 运费模版
|
||||
deliveryTemplateId: null, // 运费模版
|
||||
brandId: null, // 商品品牌
|
||||
specType: false, // 商品规格
|
||||
subCommissionType: false, // 分销类型
|
||||
|
@ -168,7 +242,7 @@ const rules = reactive({
|
|||
introduction: [required],
|
||||
picUrl: [required],
|
||||
sliderPicUrls: [required],
|
||||
// deliveryTemplateId: [required],
|
||||
deliveryTemplateId: [required],
|
||||
brandId: [required],
|
||||
specType: [required],
|
||||
subCommissionType: [required]
|
||||
|
@ -184,29 +258,10 @@ watch(
|
|||
return
|
||||
}
|
||||
copyValueToTarget(formData, data)
|
||||
formData.sliderPicUrls = data['sliderPicUrls'].map((item) => ({
|
||||
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
|
||||
}
|
||||
propertyList.value = getPropertyList(data)
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
|
@ -218,6 +273,8 @@ watch(
|
|||
*/
|
||||
const emit = defineEmits(['update:activeName'])
|
||||
const validate = async () => {
|
||||
// 校验 sku
|
||||
skuListRef.value.validateSku()
|
||||
// 校验表单
|
||||
if (!productSpuBasicInfoRef) return
|
||||
return await unref(productSpuBasicInfoRef).validate((valid) => {
|
||||
|
@ -265,12 +322,32 @@ const onChangeSpec = () => {
|
|||
}
|
||||
|
||||
const categoryList = ref([]) // 分类树
|
||||
/**
|
||||
* 选择分类时触发校验
|
||||
*/
|
||||
const categoryNodeClick = () => {
|
||||
if (!checkSelectedNode(categoryList.value, formData.categoryId)) {
|
||||
formData.categoryId = null
|
||||
message.warning('必须选择二级及以下节点!!')
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取分类的节点的完整结构
|
||||
*
|
||||
* @param categoryId 分类id
|
||||
*/
|
||||
const categoryString = (categoryId) => {
|
||||
return treeToString(categoryList.value, categoryId)
|
||||
}
|
||||
const brandList = ref([]) // 精简商品品牌列表
|
||||
const deliveryTemplateList = ref([]) // 运费模版
|
||||
onMounted(async () => {
|
||||
// 获得分类树
|
||||
const data = await ProductCategoryApi.getCategoryList({})
|
||||
categoryList.value = handleTree(data, 'id', 'parentId')
|
||||
// 获取商品品牌列表
|
||||
brandList.value = await getSimpleBrandList()
|
||||
// 获取运费模版
|
||||
deliveryTemplateList.value = await getSimpleTemplateList()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -1,30 +1,54 @@
|
|||
<template>
|
||||
<el-form ref="descriptionFormRef" :model="formData" :rules="rules" label-width="120px">
|
||||
<!-- 情况一:添加/修改 -->
|
||||
<el-form
|
||||
v-if="!isDetail"
|
||||
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>
|
||||
|
||||
<!-- 情况二:详情 -->
|
||||
<Descriptions
|
||||
v-if="isDetail"
|
||||
:data="formData"
|
||||
:schema="allSchemas.detailSchema"
|
||||
class="descriptionFormDescriptions"
|
||||
>
|
||||
<!-- 展示 HTML 内容 -->
|
||||
<template #description="{ row }">
|
||||
<div v-dompurify-html="row.description" style="width: 600px"></div>
|
||||
</template>
|
||||
</Descriptions>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { SpuType } from '@/api/mall/product/spu'
|
||||
import type { Spu } from '@/api/mall/product/spu'
|
||||
import { Editor } from '@/components/Editor'
|
||||
import { PropType } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { copyValueToTarget } from '@/utils'
|
||||
import { descriptionSchema } from './spu.data'
|
||||
|
||||
defineOptions({ name: 'DescriptionForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const { allSchemas } = useCrudSchemas(descriptionSchema)
|
||||
const props = defineProps({
|
||||
propFormData: {
|
||||
type: Object as PropType<SpuType>,
|
||||
type: Object as PropType<Spu>,
|
||||
default: () => {}
|
||||
},
|
||||
activeName: propTypes.string.def('')
|
||||
activeName: propTypes.string.def(''),
|
||||
isDetail: propTypes.bool.def(false) // 是否作为详情组件
|
||||
})
|
||||
const descriptionFormRef = ref() // 表单Ref
|
||||
const formData = ref<SpuType>({
|
||||
const formData = ref<Spu>({
|
||||
description: '' // 商品详情
|
||||
})
|
||||
// 表单规则
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
<template>
|
||||
<el-form ref="otherSettingsFormRef" :model="formData" :rules="rules" label-width="120px">
|
||||
<!-- 情况一:添加/修改 -->
|
||||
<el-form
|
||||
v-if="!isDetail"
|
||||
ref="otherSettingsFormRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-row :gutter="20">
|
||||
|
@ -50,28 +57,57 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<!-- 情况二:详情 -->
|
||||
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
|
||||
<template #recommendHot="{ row }">
|
||||
{{ row.recommendHot ? '是' : '否' }}
|
||||
</template>
|
||||
<template #recommendBenefit="{ row }">
|
||||
{{ row.recommendBenefit ? '是' : '否' }}
|
||||
</template>
|
||||
<template #recommendBest="{ row }">
|
||||
{{ row.recommendBest ? '是' : '否' }}
|
||||
</template>
|
||||
<template #recommendNew="{ row }">
|
||||
{{ row.recommendNew ? '是' : '否' }}
|
||||
</template>
|
||||
<template #recommendGood="{ row }">
|
||||
{{ row.recommendGood ? '是' : '否' }}
|
||||
</template>
|
||||
<template #activityOrders>
|
||||
<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>
|
||||
</template>
|
||||
</Descriptions>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { SpuType } from '@/api/mall/product/spu'
|
||||
import type { Spu } from '@/api/mall/product/spu'
|
||||
import { PropType } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { copyValueToTarget } from '@/utils'
|
||||
import { otherSettingsSchema } from './spu.data'
|
||||
|
||||
defineOptions({ name: 'OtherSettingsForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const { allSchemas } = useCrudSchemas(otherSettingsSchema)
|
||||
|
||||
const props = defineProps({
|
||||
propFormData: {
|
||||
type: Object as PropType<SpuType>,
|
||||
type: Object as PropType<Spu>,
|
||||
default: () => {}
|
||||
},
|
||||
activeName: propTypes.string.def('')
|
||||
activeName: propTypes.string.def(''),
|
||||
isDetail: propTypes.bool.def(false) // 是否作为详情组件
|
||||
})
|
||||
|
||||
const otherSettingsFormRef = ref() // 表单Ref
|
||||
// 表单数据
|
||||
const formData = ref<SpuType>({
|
||||
const formData = ref<Spu>({
|
||||
sort: 1, // 商品排序
|
||||
giveIntegral: 1, // 赠送积分
|
||||
virtualSalesCount: 1, // 虚拟销量
|
||||
|
|
|
@ -92,8 +92,7 @@ const submitForm = async () => {
|
|||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
name: '',
|
||||
remark: ''
|
||||
name: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
<template>
|
||||
<!-- 情况一:添加/修改 -->
|
||||
<el-table
|
||||
:data="isBatch ? skuList : formData.skus"
|
||||
v-if="!isDetail && !isActivityComponent"
|
||||
:data="isBatch ? skuList : formData!.skus"
|
||||
border
|
||||
class="tabNumWidth"
|
||||
max-height="500"
|
||||
|
@ -11,7 +13,7 @@
|
|||
<UploadImg v-model="row.picUrl" height="80px" width="100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-if="formData.specType && !isBatch">
|
||||
<template v-if="formData!.specType && !isBatch">
|
||||
<!-- 根据商品属性动态添加 -->
|
||||
<el-table-column
|
||||
v-for="(item, index) in tableHeaders"
|
||||
|
@ -21,8 +23,9 @@
|
|||
min-width="120"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<!-- TODO puhui999:展示成蓝色,有点区分度哈 -->
|
||||
{{ row.properties[index]?.valueName }}
|
||||
<span style="font-weight: bold; color: #40aaff">
|
||||
{{ row.properties[index]?.valueName }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
@ -73,7 +76,7 @@
|
|||
<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">
|
||||
<template v-if="formData!.subCommissionType">
|
||||
<el-table-column align="center" label="一级返佣(元)" min-width="168">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
|
@ -97,7 +100,7 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column v-if="formData.specType" align="center" fixed="right" label="操作" width="80">
|
||||
<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">
|
||||
批量添加
|
||||
|
@ -106,29 +109,181 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 情况二:详情 -->
|
||||
<el-table
|
||||
v-if="isDetail"
|
||||
:data="formData!.skus"
|
||||
border
|
||||
max-height="500"
|
||||
size="small"
|
||||
style="width: 99%"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column v-if="isComponent" type="selection" width="45" />
|
||||
<el-table-column align="center" label="图片" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<el-image :src="row.picUrl" class="w-60px h-60px" @click="imagePreview(row.picUrl)" />
|
||||
</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="80"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span style="font-weight: bold; color: #40aaff">
|
||||
{{ row.properties[index]?.valueName }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column align="center" label="商品条码" min-width="100">
|
||||
<template #default="{ row }">
|
||||
{{ row.barCode }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="销售价(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.price }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="市场价(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.marketPrice }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="成本价(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.costPrice }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="库存" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.stock }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="重量(kg)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.weight }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="体积(m^3)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.volume }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-if="formData!.subCommissionType">
|
||||
<el-table-column align="center" label="一级返佣(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.subCommissionFirstPrice }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="二级返佣(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.subCommissionSecondPrice }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
|
||||
<!-- 情况三:作为活动组件 -->
|
||||
<el-table
|
||||
v-if="isActivityComponent"
|
||||
:data="formData!.skus"
|
||||
border
|
||||
max-height="500"
|
||||
size="small"
|
||||
style="width: 99%"
|
||||
>
|
||||
<el-table-column v-if="isComponent" type="selection" width="45" />
|
||||
<el-table-column align="center" label="图片" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<el-image :src="row.picUrl" class="w-60px h-60px" @click="imagePreview(row.picUrl)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-if="formData!.specType">
|
||||
<!-- 根据商品属性动态添加 -->
|
||||
<el-table-column
|
||||
v-for="(item, index) in tableHeaders"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
align="center"
|
||||
min-width="80"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span style="font-weight: bold; color: #40aaff">
|
||||
{{ row.properties[index]?.valueName }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column align="center" label="商品条码" min-width="100">
|
||||
<template #default="{ row }">
|
||||
{{ row.barCode }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="销售价(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.price }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="市场价(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.marketPrice }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="成本价(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.costPrice }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="库存" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.stock }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 方便扩展每个活动配置的属性不一样 -->
|
||||
<slot name="extension"></slot>
|
||||
</el-table>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue'
|
||||
import { PropType, Ref } 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'
|
||||
import type { Property, Sku, Spu } from '@/api/mall/product/spu'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
import { RuleConfig } from '@/views/mall/product/spu/components/index'
|
||||
import { Properties } from './index'
|
||||
|
||||
defineOptions({ name: 'SkuList' })
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const props = defineProps({
|
||||
propFormData: {
|
||||
type: Object as PropType<SpuType>,
|
||||
type: Object as PropType<Spu>,
|
||||
default: () => {}
|
||||
},
|
||||
propertyList: {
|
||||
type: Array,
|
||||
type: Array as PropType<Properties[]>,
|
||||
default: () => []
|
||||
},
|
||||
isBatch: propTypes.bool.def(false) // 是否作为批量操作组件
|
||||
ruleConfig: {
|
||||
type: Array as PropType<RuleConfig[]>,
|
||||
default: () => []
|
||||
},
|
||||
isBatch: propTypes.bool.def(false), // 是否作为批量操作组件
|
||||
isDetail: propTypes.bool.def(false), // 是否作为 sku 详情组件
|
||||
isComponent: propTypes.bool.def(false), // 是否作为 sku 选择组件
|
||||
isActivityComponent: propTypes.bool.def(false) // 是否作为 sku 活动配置组件
|
||||
})
|
||||
const formData = ref<SpuType>() // 表单数据
|
||||
const skuList = ref<SkuType[]>([
|
||||
const formData: Ref<Spu | undefined> = ref<Spu>() // 表单数据
|
||||
const skuList = ref<Sku[]>([
|
||||
{
|
||||
price: 0, // 商品价格
|
||||
marketPrice: 0, // 市场价
|
||||
|
@ -142,24 +297,87 @@ const skuList = ref<SkuType[]>([
|
|||
subCommissionSecondPrice: 0 // 二级分销的佣金
|
||||
}
|
||||
]) // 批量添加时的临时数据
|
||||
// TODO @puhui999:保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
|
||||
|
||||
/** 商品图预览 */
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
createImageViewer({
|
||||
zIndex: 9999999,
|
||||
urlList: [imgUrl]
|
||||
})
|
||||
}
|
||||
|
||||
/** 批量添加 */
|
||||
const batchAdd = () => {
|
||||
formData.value.skus.forEach((item) => {
|
||||
formData.value!.skus!.forEach((item) => {
|
||||
copyValueToTarget(item, skuList.value[0])
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除 sku */
|
||||
const deleteSku = (row) => {
|
||||
const index = formData.value.skus.findIndex(
|
||||
const index = formData.value!.skus!.findIndex(
|
||||
// 直接把列表转成字符串比较
|
||||
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
|
||||
)
|
||||
formData.value.skus.splice(index, 1)
|
||||
formData.value!.skus!.splice(index, 1)
|
||||
}
|
||||
const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表头
|
||||
/**
|
||||
* 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
|
||||
*/
|
||||
const validateSku = () => {
|
||||
const checks = ['price', 'marketPrice', 'costPrice']
|
||||
let warningInfo = '请检查商品各行相关属性配置,'
|
||||
let validate = true // 默认通过
|
||||
for (const sku of formData.value!.skus!) {
|
||||
// 作为活动组件的校验
|
||||
if (props.isActivityComponent) {
|
||||
for (const rule of props.ruleConfig) {
|
||||
const arg = getValue(sku, rule.name)
|
||||
if (!rule.rule(arg)) {
|
||||
validate = false // 只要有一个不通过则直接不通过
|
||||
warningInfo += rule.message
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (checks.some((check) => sku[check] < 0.01)) {
|
||||
validate = false // 只要有一个不通过则直接不通过
|
||||
warningInfo = '商品相关价格不能低于 0.01 元!!'
|
||||
break
|
||||
}
|
||||
}
|
||||
// 只要有一个不通过则结束后续的校验
|
||||
if (!validate) {
|
||||
message.warning(warningInfo)
|
||||
throw new Error(warningInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
const getValue = (obj, arg) => {
|
||||
const keys = arg.split('.')
|
||||
let value = obj
|
||||
for (const key of keys) {
|
||||
if (value && typeof value === 'object' && key in value) {
|
||||
value = value[key]
|
||||
} else {
|
||||
value = undefined
|
||||
break
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'selectionChange', value: Sku[]): void
|
||||
}>()
|
||||
/**
|
||||
* 选择时触发
|
||||
* @param Sku 传递过来的选中的 sku 是一个数组
|
||||
*/
|
||||
const handleSelectionChange = (val: Sku[]) => {
|
||||
emit('selectionChange', val)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将传进来的值赋值给 skuList
|
||||
|
@ -187,14 +405,13 @@ const generateTableData = (propertyList: any[]) => {
|
|||
valueName: v.name
|
||||
}))
|
||||
)
|
||||
// TODO @puhui:是不是 buildSkuList,这样容易理解一点哈。item 改成 sku
|
||||
const buildList = build(propertyValues)
|
||||
const buildSkuList = build(propertyValues)
|
||||
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
|
||||
if (!validateData(propertyList)) {
|
||||
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
|
||||
formData.value!.skus = []
|
||||
}
|
||||
for (const item of buildList) {
|
||||
for (const item of buildSkuList) {
|
||||
const row = {
|
||||
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象
|
||||
price: 0,
|
||||
|
@ -209,13 +426,13 @@ const generateTableData = (propertyList: any[]) => {
|
|||
subCommissionSecondPrice: 0
|
||||
}
|
||||
// 如果存在属性相同的 sku 则不做处理
|
||||
const index = formData.value!.skus.findIndex(
|
||||
const index = formData.value!.skus!.findIndex(
|
||||
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
|
||||
)
|
||||
if (index !== -1) {
|
||||
continue
|
||||
}
|
||||
formData.value.skus.push(row)
|
||||
formData.value!.skus!.push(row)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,13 +440,13 @@ const generateTableData = (propertyList: any[]) => {
|
|||
* 生成 skus 前置校验
|
||||
*/
|
||||
const validateData = (propertyList: any[]) => {
|
||||
const skuPropertyIds = []
|
||||
formData.value.skus.forEach((sku) =>
|
||||
const skuPropertyIds: number[] = []
|
||||
formData.value!.skus!.forEach((sku) =>
|
||||
sku.properties
|
||||
?.map((property) => property.propertyId)
|
||||
.forEach((propertyId) => {
|
||||
if (skuPropertyIds.indexOf(propertyId) === -1) {
|
||||
skuPropertyIds.push(propertyId)
|
||||
?.forEach((propertyId) => {
|
||||
if (skuPropertyIds.indexOf(propertyId!) === -1) {
|
||||
skuPropertyIds.push(propertyId!)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
@ -263,9 +480,9 @@ const build = (propertyValuesList: Property[][]) => {
|
|||
/** 监听属性列表,生成相关参数和表头 */
|
||||
watch(
|
||||
() => props.propertyList,
|
||||
(propertyList) => {
|
||||
(propertyList: Properties[]) => {
|
||||
// 如果不是多规格则结束
|
||||
if (!formData.value.specType) {
|
||||
if (!formData.value!.specType) {
|
||||
return
|
||||
}
|
||||
// 如果当前组件作为批量添加数据使用,则重置表数据
|
||||
|
@ -303,7 +520,7 @@ watch(
|
|||
return
|
||||
}
|
||||
// 添加新属性没有属性值也不做处理
|
||||
if (propertyList.some((item) => item.values.length === 0)) {
|
||||
if (propertyList.some((item) => item.values!.length === 0)) {
|
||||
return
|
||||
}
|
||||
// 生成 table 数据,即 sku 列表
|
||||
|
@ -315,5 +532,5 @@ watch(
|
|||
}
|
||||
)
|
||||
// 暴露出生成 sku 方法,给添加属性成功时调用
|
||||
defineExpose({ generateTableData })
|
||||
defineExpose({ generateTableData, validateSku })
|
||||
</script>
|
||||
|
|
|
@ -2,14 +2,70 @@ 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 ProductPropertyAddForm from './ProductPropertyAddForm.vue'
|
||||
import SkuList from './SkuList.vue'
|
||||
|
||||
import { Spu } from '@/api/mall/product/spu'
|
||||
|
||||
// TODO @puhui999:Properties 改成 Property 更合适?
|
||||
interface Properties {
|
||||
id: number
|
||||
name: string
|
||||
values?: Properties[]
|
||||
}
|
||||
|
||||
interface RuleConfig {
|
||||
// 需要校验的字段
|
||||
// 例:name: 'name' 则表示校验 sku.name 的值
|
||||
// 例:name: 'productConfig.stock' 则表示校验 sku.productConfig.name 的值,此处 productConfig 表示我在 Sku 上扩展的属性
|
||||
name: string
|
||||
// 校验规格为一个毁掉函数,其中 arg 为需要校验的字段的值。
|
||||
// 例:需要校验价格必须大于0.01
|
||||
// {
|
||||
// name:'price',
|
||||
// rule:(arg) => arg > 0.01
|
||||
// }
|
||||
rule: (arg: any) => boolean
|
||||
// 校验不通过时的消息提示
|
||||
message: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得商品的规格列表
|
||||
*
|
||||
* @param spu
|
||||
* @return Property 规格列表
|
||||
*/
|
||||
const getPropertyList = (spu: Spu): Properties[] => {
|
||||
// 直接拿返回的 skus 属性逆向生成出 propertyList
|
||||
const properties: Properties[] = []
|
||||
// 只有是多规格才处理
|
||||
if (spu.specType) {
|
||||
spu.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! })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
return properties
|
||||
}
|
||||
|
||||
export {
|
||||
BasicInfoForm,
|
||||
DescriptionForm,
|
||||
OtherSettingsForm,
|
||||
ProductAttributes,
|
||||
ProductAttributesAddForm,
|
||||
SkuList
|
||||
ProductPropertyAddForm,
|
||||
SkuList,
|
||||
getPropertyList,
|
||||
Properties,
|
||||
RuleConfig
|
||||
}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
import { CrudSchema } from '@/hooks/web/useCrudSchemas'
|
||||
|
||||
export const basicInfoSchema = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '商品名称',
|
||||
field: 'name'
|
||||
},
|
||||
{
|
||||
label: '关键字',
|
||||
field: 'keyword'
|
||||
},
|
||||
{
|
||||
label: '商品简介',
|
||||
field: 'introduction'
|
||||
},
|
||||
{
|
||||
label: '商品分类',
|
||||
field: 'categoryId'
|
||||
},
|
||||
{
|
||||
label: '商品品牌',
|
||||
field: 'brandId'
|
||||
},
|
||||
{
|
||||
label: '商品封面图',
|
||||
field: 'picUrl'
|
||||
},
|
||||
{
|
||||
label: '商品轮播图',
|
||||
field: 'sliderPicUrls'
|
||||
},
|
||||
{
|
||||
label: '商品视频',
|
||||
field: 'videoUrl'
|
||||
},
|
||||
{
|
||||
label: '单位',
|
||||
field: 'unit',
|
||||
dictType: DICT_TYPE.PRODUCT_UNIT
|
||||
},
|
||||
{
|
||||
label: '规格类型',
|
||||
field: 'specType'
|
||||
},
|
||||
{
|
||||
label: '分销类型',
|
||||
field: 'subCommissionType'
|
||||
},
|
||||
{
|
||||
label: '物流模版',
|
||||
field: 'deliveryTemplateId'
|
||||
},
|
||||
{
|
||||
label: '商品属性列表',
|
||||
field: 'skus'
|
||||
}
|
||||
])
|
||||
export const descriptionSchema = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '商品详情',
|
||||
field: 'description'
|
||||
}
|
||||
])
|
||||
export const otherSettingsSchema = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '商品排序',
|
||||
field: 'sort'
|
||||
},
|
||||
{
|
||||
label: '赠送积分',
|
||||
field: 'giveIntegral'
|
||||
},
|
||||
{
|
||||
label: '虚拟销量',
|
||||
field: 'virtualSalesCount'
|
||||
},
|
||||
{
|
||||
label: '是否热卖推荐',
|
||||
field: 'recommendHot'
|
||||
},
|
||||
{
|
||||
label: '是否优惠推荐',
|
||||
field: 'recommendBenefit'
|
||||
},
|
||||
{
|
||||
label: '是否精品推荐',
|
||||
field: 'recommendBest'
|
||||
},
|
||||
{
|
||||
label: '是否新品推荐',
|
||||
field: 'recommendNew'
|
||||
},
|
||||
{
|
||||
label: '是否优品推荐',
|
||||
field: 'recommendGood'
|
||||
},
|
||||
{
|
||||
label: '赠送的优惠劵',
|
||||
field: 'giveCouponTemplateIds'
|
||||
},
|
||||
{
|
||||
label: '活动显示排序',
|
||||
field: 'activityOrders'
|
||||
}
|
||||
])
|
|
@ -8,18 +8,15 @@
|
|||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<!-- TODO @puhui999:品牌应该是数据下拉哈 -->
|
||||
<el-form-item label="品牌名称" prop="name">
|
||||
<el-form-item label="商品名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入品牌名称"
|
||||
placeholder="请输入商品名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- TODO 分类只能选择二级分类目前还没做,还是先以联调通顺为主 -->
|
||||
<!-- TODO puhui999:我们要不改成支持选择一级。如果选择一级,后端要递归查询下子分类,然后去 in? -->
|
||||
<el-form-item label="商品分类" prop="categoryId">
|
||||
<el-tree-select
|
||||
v-model="queryParams.categoryId"
|
||||
|
@ -29,6 +26,7 @@
|
|||
class="w-1/1"
|
||||
node-key="id"
|
||||
placeholder="请选择商品分类"
|
||||
@change="nodeClick"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
|
@ -80,31 +78,54 @@
|
|||
/>
|
||||
</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 class="demo-table-expand" label-position="left">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="商品分类:">
|
||||
<span>{{ categoryString(row.categoryId) }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="市场价:">
|
||||
<span>{{ formatToFraction(row.marketPrice) }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="成本价:">
|
||||
<span>{{ formatToFraction(row.costPrice) }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="收藏:">
|
||||
<!-- TODO 没有这个属性,暂时写死 5 个 -->
|
||||
<span>5</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="虚拟销量:">
|
||||
<span>{{ row.virtualSalesCount }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</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" />
|
||||
<el-image :src="row.picUrl" class="w-30px h-30px" @click="imagePreview(row.picUrl)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
|
||||
|
@ -143,8 +164,12 @@
|
|||
</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
|
||||
v-hasPermi="['product:spu:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openDetail(row.id)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<template v-if="queryParams.tabType === 4">
|
||||
|
@ -202,7 +227,7 @@ 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 { checkSelectedNode, defaultProps, handleTree, treeToString } from '@/utils/tree'
|
||||
import { ProductSpuStatusEnum } from '@/utils/constants'
|
||||
import { formatToFraction } from '@/utils'
|
||||
import download from '@/utils/download'
|
||||
|
@ -258,12 +283,15 @@ const getTabsCount = async () => {
|
|||
const queryParams = ref({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
tabType: 0
|
||||
tabType: 0,
|
||||
name: '',
|
||||
categoryId: null,
|
||||
createTime: []
|
||||
}) // 查询参数
|
||||
const queryFormRef = ref() // 搜索的表单Ref
|
||||
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.value.tabType = tab.paneName
|
||||
queryParams.value.tabType = tab.paneName as number
|
||||
getList()
|
||||
}
|
||||
|
||||
|
@ -364,18 +392,18 @@ const resetQuery = () => {
|
|||
const openForm = (id?: number) => {
|
||||
// 修改
|
||||
if (typeof id === 'number') {
|
||||
push('/product/productSpuEdit/' + id)
|
||||
push('/product/spu/edit/' + id)
|
||||
return
|
||||
}
|
||||
// 新增
|
||||
push('/product/productSpuAdd')
|
||||
push({ name: 'ProductSpuAdd' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看商品详情
|
||||
*/
|
||||
const openDetail = () => {
|
||||
message.alert('查看详情未完善!!!')
|
||||
const openDetail = (id?: number) => {
|
||||
push('/product/spu/detail/' + id)
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
|
@ -393,7 +421,7 @@ const handleExport = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
// 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新
|
||||
// 监听路由变化更新列表,解决商品保存后,列表不刷新的问题。
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
() => {
|
||||
|
@ -402,6 +430,24 @@ watch(
|
|||
)
|
||||
|
||||
const categoryList = ref() // 分类树
|
||||
/**
|
||||
* 获取分类的节点的完整结构
|
||||
* @param categoryId 分类id
|
||||
*/
|
||||
const categoryString = (categoryId) => {
|
||||
return treeToString(categoryList.value, categoryId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验所选是否为二级及以下节点
|
||||
*/
|
||||
const nodeClick = () => {
|
||||
if (!checkSelectedNode(categoryList.value, queryParams.value.categoryId)) {
|
||||
queryParams.value.categoryId = null
|
||||
message.warning('必须选择二级及以下节点!!')
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTabsCount()
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
<template>
|
||||
<el-table :data="spuData" :default-expand-all="true">
|
||||
<el-table-column type="expand" width="30">
|
||||
<template #default="{ row }">
|
||||
<SkuList
|
||||
ref="skuListRef"
|
||||
:is-activity-component="true"
|
||||
:prop-form-data="spuPropertyList.find((item) => item.spuId === row.id)?.spuDetail"
|
||||
:property-list="spuPropertyList.find((item) => item.spuId === row.id)?.propertyList"
|
||||
:rule-config="ruleConfig"
|
||||
>
|
||||
<template #extension>
|
||||
<el-table-column align="center" label="秒杀库存" min-width="168">
|
||||
<template #default="{ row: sku }">
|
||||
<el-input-number v-model="sku.productConfig.stock" :min="0" class="w-100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="秒杀价格(元)" min-width="168">
|
||||
<template #default="{ row: sku }">
|
||||
<el-input-number
|
||||
v-model="sku.productConfig.seckillPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.1"
|
||||
class="w-100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</SkuList>
|
||||
</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" class="w-30px h-30px" @click="imagePreview(row.picUrl)" />
|
||||
</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>
|
||||
</template>
|
||||
<script generic="T extends Spu" lang="ts" setup>
|
||||
// TODO 后续计划重新封装作为活动商品配置通用组件;可以等其他活动做到的时候,在统一处理 SPU 选择组件哈
|
||||
import { formatToFraction } from '@/utils'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
import { Spu } from '@/api/mall/product/spu'
|
||||
import { RuleConfig, SkuList } from '@/views/mall/product/spu/components'
|
||||
import { SeckillProductVO } from '@/api/mall/promotion/seckill/seckillActivity'
|
||||
import { SpuProperty } from '@/views/mall/promotion/components/index'
|
||||
|
||||
defineOptions({ name: 'PromotionSpuAndSkuList' })
|
||||
|
||||
// TODO @puhui999:是不是改成传递一个 spu 就好啦? 因为活动商品可以多选所以展示编辑的时候需要展示多个
|
||||
const props = defineProps<{
|
||||
spuList: T[]
|
||||
ruleConfig: RuleConfig[]
|
||||
spuPropertyListP: SpuProperty<T>[]
|
||||
}>()
|
||||
|
||||
const spuData = ref<Spu[]>([]) // spu 详情数据列表
|
||||
const skuListRef = ref() // 商品属性列表Ref
|
||||
|
||||
const spuPropertyList = ref<SpuProperty<T>[]>([]) // spuId 对应的 sku 的属性列表
|
||||
|
||||
/**
|
||||
* 获取所有 sku 秒杀配置
|
||||
* @param extendedAttribute 在 sku 上扩展的属性,例:秒杀活动 sku 扩展属性 productConfig 请参考 seckillActivity.ts
|
||||
*/
|
||||
const getSkuConfigs: <V>(extendedAttribute: string) => V[] = (extendedAttribute: string) => {
|
||||
skuListRef.value.validateSku()
|
||||
const seckillProducts: SeckillProductVO[] = []
|
||||
spuPropertyList.value.forEach((item) => {
|
||||
item.spuDetail.skus.forEach((sku) => {
|
||||
seckillProducts.push(sku[extendedAttribute])
|
||||
})
|
||||
})
|
||||
return seckillProducts
|
||||
}
|
||||
// 暴露出给表单提交时使用
|
||||
defineExpose({ getSkuConfigs })
|
||||
|
||||
/** 商品图预览 */
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
createImageViewer({
|
||||
zIndex: 99999999,
|
||||
urlList: [imgUrl]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 将传进来的值赋值给 skuList
|
||||
*/
|
||||
watch(
|
||||
() => props.spuList,
|
||||
(data) => {
|
||||
if (!data) return
|
||||
spuData.value = data as Spu[]
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
/**
|
||||
* 将传进来的值赋值给 skuList
|
||||
*/
|
||||
watch(
|
||||
() => props.spuPropertyListP,
|
||||
(data) => {
|
||||
if (!data) return
|
||||
spuPropertyList.value = data as SpuProperty<T>[]
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
</script>
|
|
@ -0,0 +1,266 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" :appendToBody="true" :title="dialogTitle" width="70%">
|
||||
<ContentWrap>
|
||||
<el-row :gutter="20" class="mb-10px">
|
||||
<el-col :span="6">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-tree-select
|
||||
v-model="queryParams.categoryId"
|
||||
:data="categoryList"
|
||||
:props="defaultProps"
|
||||
check-strictly
|
||||
class="w-1/1"
|
||||
node-key="id"
|
||||
placeholder="请选择商品分类"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<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-col>
|
||||
<el-col :span="6">
|
||||
<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-col>
|
||||
</el-row>
|
||||
<el-table
|
||||
ref="spuListRef"
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:expand-row-keys="expandRowKeys"
|
||||
row-key="id"
|
||||
@expand-change="expandChange"
|
||||
@selection-change="selectSpu"
|
||||
>
|
||||
<el-table-column v-if="isSelectSku" type="expand" width="30">
|
||||
<template #default>
|
||||
<SkuList
|
||||
v-if="isExpand"
|
||||
:isComponent="true"
|
||||
:isDetail="true"
|
||||
:prop-form-data="spuData"
|
||||
:property-list="propertyList"
|
||||
@selection-change="selectSku"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<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" class="w-30px h-30px" @click="imagePreview(row.picUrl)" />
|
||||
</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>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="confirm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getPropertyList, Properties, SkuList } from '@/views/mall/product/spu/components'
|
||||
import { ElTable } from 'element-plus'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
import { formatToFraction } from '@/utils'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
|
||||
import * as ProductCategoryApi from '@/api/mall/product/category'
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
|
||||
defineOptions({ name: 'PromotionSpuSelect' })
|
||||
|
||||
const props = defineProps({
|
||||
// 默认不需要(不需要的情况下只返回 spu,需要的情况下返回 选中的 spu 和 sku 列表)
|
||||
// 其它活动需要选择商品和商品属性导入此组件即可,需添加组件属性 :isSelectSku='true'
|
||||
isSelectSku: propTypes.bool.def(false) // 是否需要选择 sku 属性
|
||||
})
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref<any[]>([]) // 列表的数据
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const queryParams = ref({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
tabType: 0, // 默认获取上架的商品
|
||||
name: '',
|
||||
categoryId: null,
|
||||
createTime: []
|
||||
}) // 查询参数
|
||||
const propertyList = ref<Properties[]>([]) // 商品属性列表
|
||||
const spuListRef = ref<InstanceType<typeof ElTable>>()
|
||||
const spuData = ref<ProductSpuApi.Spu | {}>() // 商品详情
|
||||
const isExpand = ref(false) // 控制 SKU 列表显示
|
||||
const expandRowKeys = ref<number[]>() // 控制展开行需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。
|
||||
|
||||
// 计算商品属性
|
||||
const expandChange = async (row: ProductSpuApi.Spu, expandedRows: ProductSpuApi.Spu[]) => {
|
||||
spuData.value = {}
|
||||
propertyList.value = []
|
||||
isExpand.value = false
|
||||
// 如果展开个数为 0
|
||||
if (expandedRows.length === 0) {
|
||||
expandRowKeys.value = []
|
||||
return
|
||||
}
|
||||
// 获取 SPU 详情
|
||||
const res = (await ProductSpuApi.getSpu(row.id as number)) as ProductSpuApi.Spu
|
||||
propertyList.value = getPropertyList(res)
|
||||
spuData.value = res
|
||||
isExpand.value = true
|
||||
expandRowKeys.value = [row.id!]
|
||||
}
|
||||
|
||||
//============ 商品选择相关 ============
|
||||
const selectedSpuIds = ref<number[]>([]) // 选中的商品 spuIds
|
||||
const selectedSkuIds = ref<number[]>([]) // 选中的商品 skuIds
|
||||
const selectSku = (val: ProductSpuApi.Sku[]) => {
|
||||
selectedSkuIds.value = val.map((sku) => sku.id!)
|
||||
}
|
||||
const selectSpu = (val: ProductSpuApi.Spu[]) => {
|
||||
selectedSpuIds.value = val.map((spu) => spu.id!)
|
||||
// // 只选择一个
|
||||
// selectedSpu.value = val[0]
|
||||
// // 如果大于1个
|
||||
// if (val.length > 1) {
|
||||
// // 清空选择
|
||||
// spuListRef.value.clearSelection()
|
||||
// // 变更为最后一次选择的
|
||||
// spuListRef.value.toggleRowSelection(val.pop(), true)
|
||||
// }
|
||||
}
|
||||
// 确认选择时的触发事件
|
||||
const emits = defineEmits<{
|
||||
(e: 'confirm', spuIds: number[], skuIds?: number[]): void
|
||||
}>()
|
||||
/**
|
||||
* 确认选择返回选中的 spu 和 sku (如果需要选择sku的话)
|
||||
*/
|
||||
const confirm = () => {
|
||||
if (selectedSpuIds.value.length === 0) {
|
||||
message.warning('没有选择任何商品')
|
||||
return
|
||||
}
|
||||
if (props.isSelectSku && selectedSkuIds.value.length === 0) {
|
||||
message.warning('没有选择任何商品属性')
|
||||
return
|
||||
}
|
||||
// 返回各自 id 列表
|
||||
props.isSelectSku
|
||||
? emits('confirm', selectedSpuIds.value, selectedSkuIds.value)
|
||||
: emits('confirm', selectedSpuIds.value)
|
||||
// 关闭弹窗
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = () => {
|
||||
dialogTitle.value = '商品选择'
|
||||
dialogVisible.value = true
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 查询列表 */
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryParams.value = {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
tabType: 0, // 默认获取上架的商品
|
||||
name: '',
|
||||
categoryId: null,
|
||||
createTime: []
|
||||
}
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 商品图预览 */
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
createImageViewer({
|
||||
zIndex: 99999999,
|
||||
urlList: [imgUrl]
|
||||
})
|
||||
}
|
||||
|
||||
const categoryList = ref() // 分类树
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
// 获得分类树
|
||||
const data = await ProductCategoryApi.getCategoryList({})
|
||||
categoryList.value = handleTree(data, 'id', 'parentId')
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,14 @@
|
|||
import SpuSelect from './SpuSelect.vue'
|
||||
import SpuAndSkuList from './SpuAndSkuList.vue'
|
||||
import { Properties } from '@/views/mall/product/spu/components'
|
||||
|
||||
type SpuProperty<T> = {
|
||||
spuId: number
|
||||
spuDetail: T
|
||||
propertyList: Properties[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供商品活动商品选择通用组件
|
||||
*/
|
||||
export { SpuSelect, SpuAndSkuList, SpuProperty }
|
|
@ -0,0 +1,200 @@
|
|||
<template>
|
||||
<doc-alert title="功能开启" url="https://doc.iocoder.cn/mall/build/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||
<el-form-item label="会员昵称" prop="nickname">
|
||||
<el-input
|
||||
v-model="queryParams.nickname"
|
||||
placeholder="请输入会员昵称"
|
||||
clearable
|
||||
@keyup="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
style="width: 240px"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @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-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作工具栏 -->
|
||||
<!-- <el-row :gutter="10" class="mb8">
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
|
||||
</el-row> -->
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap>
|
||||
<!-- Tab 选项:真正的内容在 Lab -->
|
||||
<el-tabs v-model="activeTab" type="card" @tab-change="onTabChange">
|
||||
<el-tab-pane
|
||||
v-for="tab in statusTabs"
|
||||
:key="tab.value"
|
||||
:label="tab.label"
|
||||
:name="tab.value"
|
||||
/>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 列表 -->
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="会员信息" align="center" prop="nickname" />
|
||||
<!-- TODO 芋艿:以后支持头像,支持跳转 -->
|
||||
<el-table-column label="优惠劵" align="center" prop="name" />
|
||||
<el-table-column label="优惠券类型" align="center" prop="discountType">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="领取方式" align="center" prop="takeType">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE" :value="scope.row.takeType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PROMOTION_COUPON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="领取时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
label="使用时间"
|
||||
align="center"
|
||||
prop="useTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['promotion:coupon:delete']"
|
||||
><Icon icon="ep:delete" :size="12" class="mr-1px" />回收</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PromotionCoupon">
|
||||
import { deleteCoupon, getCouponPage } from '@/api/mall/promotion/coupon'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { FormInstance } from 'element-plus'
|
||||
|
||||
// 消息弹窗
|
||||
const message = useMessage()
|
||||
|
||||
// 遮罩层
|
||||
const loading = ref(true)
|
||||
// 总条数
|
||||
const total = ref(0)
|
||||
// 优惠劵列表
|
||||
const list = ref([])
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
createTime: [],
|
||||
status: undefined
|
||||
})
|
||||
// Tab 筛选
|
||||
const activeTab = ref('all')
|
||||
|
||||
const statusTabs = reactive([
|
||||
{
|
||||
label: '全部',
|
||||
value: 'all'
|
||||
}
|
||||
])
|
||||
|
||||
const queryFormRef = ref<FormInstance | null>(null)
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
// 执行查询
|
||||
try {
|
||||
const data = await getCouponPage(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 handleDelete = async (row) => {
|
||||
const id = row.id
|
||||
|
||||
try {
|
||||
await message.confirm(
|
||||
'回收将会收回会员领取的待使用的优惠券,已使用的将无法回收,确定要回收所选优惠券吗?'
|
||||
)
|
||||
await deleteCoupon(id)
|
||||
getList()
|
||||
message.notifySuccess('回收成功')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** tab 切换 */
|
||||
const onTabChange = (tabName) => {
|
||||
queryParams.status = tabName === 'all' ? undefined : tabName
|
||||
getList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
// 设置 statuses 过滤
|
||||
for (const dict of getIntDictOptions(DICT_TYPE.PROMOTION_COUPON_STATUS)) {
|
||||
statusTabs.push({
|
||||
label: dict.label,
|
||||
value: dict.value as string
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,614 @@
|
|||
<template>
|
||||
<doc-alert title="功能开启" url="https://doc.iocoder.cn/mall/build/" />
|
||||
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
v-show="showSearch"
|
||||
label-width="82px"
|
||||
>
|
||||
<el-form-item label="优惠券名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入优惠劵名"
|
||||
clearable
|
||||
@keyup="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="优惠券类型" prop="discountType">
|
||||
<el-select v-model="queryParams.discountType" placeholder="请选择优惠券类型" clearable>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="优惠券状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择优惠券状态" clearable>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
style="width: 240px"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @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-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作工具栏 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['promotion:coupon-template:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" />新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
@click="$router.push('/promotion/coupon')"
|
||||
v-hasPermi="['promotion:coupon:query']"
|
||||
>
|
||||
<Icon icon="ep:operation" class="mr-5px" />会员优惠劵
|
||||
</el-button>
|
||||
</el-col>
|
||||
<!-- <right-toolbar v-model:showSearch="showSearch" @query-table="getList" /> -->
|
||||
</el-row>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="优惠券名称" align="center" prop="name" />
|
||||
<el-table-column label="优惠券类型" align="center" prop="discountType">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="优惠金额 / 折扣"
|
||||
align="center"
|
||||
prop="discount"
|
||||
:formatter="discountFormat"
|
||||
/>
|
||||
<el-table-column label="发放数量" align="center" prop="totalCount" />
|
||||
<el-table-column
|
||||
label="剩余数量"
|
||||
align="center"
|
||||
prop="totalCount"
|
||||
:formatter="(row) => row.totalCount - row.takeCount"
|
||||
/>
|
||||
<el-table-column
|
||||
label="领取上限"
|
||||
align="center"
|
||||
prop="takeLimitCount"
|
||||
:formatter="takeLimitCountFormat"
|
||||
/>
|
||||
<el-table-column
|
||||
label="有效期限"
|
||||
align="center"
|
||||
prop="validityType"
|
||||
width="180"
|
||||
:formatter="validityTypeFormat"
|
||||
/>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.status"
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['promotion:coupon-template:update']"
|
||||
>
|
||||
<Icon icon="ep:edit" :size="12" class="mr-1px" />
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['promotion:coupon-template:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" :size="12" class="mr-1px" />
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px">
|
||||
<el-form-item label="优惠券名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入优惠券名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="优惠券类型" prop="discountType">
|
||||
<el-radio-group v-model="form.discountType">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="parseInt(dict.value)"
|
||||
>{{ dict.label }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.discountType === PromotionDiscountTypeEnum.PRICE.type"
|
||||
label="优惠券面额"
|
||||
prop="discountPrice"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="form.discountPrice"
|
||||
placeholder="请输入优惠金额,单位:元"
|
||||
style="width: 400px"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
/>
|
||||
元
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.discountType === PromotionDiscountTypeEnum.PERCENT.type"
|
||||
label="优惠券折扣"
|
||||
prop="discountPercent"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="form.discountPercent"
|
||||
placeholder="优惠券折扣不能小于 1 折,且不可大于 9.9 折"
|
||||
style="width: 400px"
|
||||
:precision="1"
|
||||
:min="1"
|
||||
:max="9.9"
|
||||
/>
|
||||
折
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.discountType === PromotionDiscountTypeEnum.PERCENT.type"
|
||||
label="最多优惠"
|
||||
prop="discountLimitPrice"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="form.discountLimitPrice"
|
||||
placeholder="请输入最多优惠"
|
||||
style="width: 400px"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
/>
|
||||
元
|
||||
</el-form-item>
|
||||
<el-form-item label="满多少元可以使用" prop="usePrice">
|
||||
<el-input-number
|
||||
v-model="form.usePrice"
|
||||
placeholder="无门槛请设为 0"
|
||||
style="width: 400px"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
/>
|
||||
元
|
||||
</el-form-item>
|
||||
<el-form-item label="领取方式" prop="takeType">
|
||||
<el-radio-group v-model="form.takeType">
|
||||
<el-radio :key="1" :label="1">直接领取</el-radio>
|
||||
<el-radio :key="2" :label="2">指定发放</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.takeType === 1" label="发放数量" prop="totalCount">
|
||||
<el-input-number
|
||||
v-model="form.totalCount"
|
||||
placeholder="发放数量,没有之后不能领取或发放,-1 为不限制"
|
||||
style="width: 400px"
|
||||
:precision="0"
|
||||
:min="-1"
|
||||
/>
|
||||
张
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.takeType === 1" label="每人限领个数" prop="takeLimitCount">
|
||||
<el-input-number
|
||||
v-model="form.takeLimitCount"
|
||||
placeholder="设置为 -1 时,可无限领取"
|
||||
style="width: 400px"
|
||||
:precision="0"
|
||||
:min="-1"
|
||||
/>
|
||||
张
|
||||
</el-form-item>
|
||||
<el-form-item label="有效期类型" prop="validityType">
|
||||
<el-radio-group v-model="form.validityType">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="parseInt(dict.value)"
|
||||
>{{ dict.label }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.validityType === CouponTemplateValidityTypeEnum.DATE.type"
|
||||
label="固定日期"
|
||||
prop="validTimes"
|
||||
>
|
||||
<el-date-picker
|
||||
v-model="form.validTimes"
|
||||
style="width: 240px"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
type="datetimerange"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.validityType === CouponTemplateValidityTypeEnum.TERM.type"
|
||||
label="领取日期"
|
||||
prop="fixedStartTerm"
|
||||
>
|
||||
第
|
||||
<el-input-number
|
||||
v-model="form.fixedStartTerm"
|
||||
placeholder="0 为今天生效"
|
||||
style="width: 165px"
|
||||
:precision="0"
|
||||
:min="0"
|
||||
/>
|
||||
至
|
||||
<el-input-number
|
||||
v-model="form.fixedEndTerm"
|
||||
placeholder="请输入结束天数"
|
||||
style="width: 165px"
|
||||
:precision="0"
|
||||
:min="0"
|
||||
/>
|
||||
天有效
|
||||
</el-form-item>
|
||||
<el-form-item label="活动商品" prop="productScope">
|
||||
<el-radio-group v-model="form.productScope">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_PRODUCT_SCOPE)"
|
||||
:key="dict.value"
|
||||
:label="parseInt(dict.value)"
|
||||
>{{ dict.label }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.productScope === PromotionProductScopeEnum.SPU.scope"
|
||||
prop="productSpuIds"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.productSpuIds"
|
||||
placeholder="请选择活动商品"
|
||||
clearable
|
||||
size="small"
|
||||
multiple
|
||||
filterable
|
||||
style="width: 400px"
|
||||
>
|
||||
<el-option v-for="item in productSpus" :key="item.id" :label="item.name" :value="item.id">
|
||||
<span style="float: left">{{ item.name }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px"
|
||||
>¥{{ (item.minPrice / 100.0).toFixed(2) }}</span
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PromotionCouponTemplate">
|
||||
import {
|
||||
createCouponTemplate,
|
||||
updateCouponTemplate,
|
||||
deleteCouponTemplate,
|
||||
getCouponTemplate,
|
||||
getCouponTemplatePage,
|
||||
updateCouponTemplateStatus
|
||||
} from '@/api/mall/promotion/couponTemplate'
|
||||
import {
|
||||
CommonStatusEnum,
|
||||
CouponTemplateValidityTypeEnum,
|
||||
PromotionDiscountTypeEnum,
|
||||
PromotionProductScopeEnum
|
||||
} from '@/utils/constants'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { getSpuSimpleList } from '@/api/mall/product/spu'
|
||||
import { dateFormatter, formatDate } from '@/utils/formatTime'
|
||||
import { FormInstance } from 'element-plus'
|
||||
|
||||
// 消息弹窗
|
||||
const message = useMessage()
|
||||
|
||||
// 遮罩层
|
||||
const loading = ref(true)
|
||||
// 显示搜索条件
|
||||
const showSearch = ref(true)
|
||||
// 总条数
|
||||
const total = ref(0)
|
||||
// 优惠劵列表
|
||||
const list = ref([])
|
||||
// 弹出层标题
|
||||
const title = ref('')
|
||||
// 是否显示弹出层
|
||||
const open = ref(false)
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
status: null,
|
||||
type: null,
|
||||
createTime: []
|
||||
})
|
||||
// 表单参数
|
||||
const form = ref<any>({})
|
||||
// 表单校验
|
||||
const rules = {
|
||||
name: [{ required: true, message: '优惠券名称不能为空', trigger: 'blur' }],
|
||||
discountType: [{ required: true, message: '优惠券类型不能为空', trigger: 'change' }],
|
||||
discountPrice: [{ required: true, message: '优惠券面额不能为空', trigger: 'blur' }],
|
||||
discountPercent: [{ required: true, message: '优惠券折扣不能为空', trigger: 'blur' }],
|
||||
discountLimitPrice: [{ required: true, message: '最多优惠不能为空', trigger: 'blur' }],
|
||||
usePrice: [{ required: true, message: '满多少元可以使用不能为空', trigger: 'blur' }],
|
||||
takeType: [{ required: true, message: '领取方式不能为空', trigger: 'change' }],
|
||||
totalCount: [{ required: true, message: '发放数量不能为空', trigger: 'blur' }],
|
||||
takeLimitCount: [{ required: true, message: '每人限领个数不能为空', trigger: 'blur' }],
|
||||
validityType: [{ required: true, message: '有效期类型不能为空', trigger: 'change' }],
|
||||
validTimes: [{ required: true, message: '固定日期不能为空', trigger: 'change' }],
|
||||
fixedStartTerm: [{ required: true, message: '开始领取天数不能为空', trigger: 'blur' }],
|
||||
fixedEndTerm: [{ required: true, message: '开始领取天数不能为空', trigger: 'blur' }],
|
||||
productScope: [{ required: true, message: '商品范围不能为空', trigger: 'blur' }],
|
||||
productSpuIds: [{ required: true, message: '商品范围不能为空', trigger: 'blur' }]
|
||||
}
|
||||
// 商品列表
|
||||
const productSpus = ref([])
|
||||
const queryFormRef = ref<FormInstance | null>(null)
|
||||
const formRef = ref<FormInstance | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 执行查询
|
||||
const data = await getCouponTemplatePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
// 查询商品列表
|
||||
productSpus.value = await getSpuSimpleList()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
open.value = false
|
||||
reset()
|
||||
}
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
discountType: PromotionDiscountTypeEnum.PRICE.type,
|
||||
discountPrice: undefined,
|
||||
discountPercent: undefined,
|
||||
discountLimitPrice: undefined,
|
||||
usePrice: undefined,
|
||||
takeType: 1,
|
||||
totalCount: undefined,
|
||||
takeLimitCount: undefined,
|
||||
validityType: CouponTemplateValidityTypeEnum.DATE.type,
|
||||
validTimes: [],
|
||||
validStartTime: undefined,
|
||||
validEndTime: undefined,
|
||||
fixedStartTerm: undefined,
|
||||
fixedEndTerm: undefined,
|
||||
productScope: PromotionProductScopeEnum.ALL.scope,
|
||||
productSpuIds: []
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef?.value?.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset()
|
||||
open.value = true
|
||||
title.value = '添加优惠劵'
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row: any) => {
|
||||
reset()
|
||||
const id = row.id
|
||||
try {
|
||||
const data = await getCouponTemplate(id)
|
||||
form.value = {
|
||||
...data,
|
||||
discountPrice: data.discountPrice !== undefined ? data.discountPrice / 100.0 : undefined,
|
||||
discountPercent: data.discountPercent !== undefined ? data.discountPercent / 10.0 : undefined,
|
||||
discountLimitPrice:
|
||||
data.discountLimitPrice !== undefined ? data.discountLimitPrice / 100.0 : undefined,
|
||||
usePrice: data.usePrice !== undefined ? data.usePrice / 100.0 : undefined,
|
||||
validTimes: [data.validStartTime, data.validEndTime]
|
||||
}
|
||||
open.value = true
|
||||
title.value = '修改优惠劵'
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = async () => {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
|
||||
// 金额相关字段的缩放
|
||||
let data = {
|
||||
...form.value,
|
||||
discountPrice:
|
||||
form.value.discountPrice !== undefined ? form.value.discountPrice * 100 : undefined,
|
||||
discountPercent:
|
||||
form.value.discountPercent !== undefined ? form.value.discountPercent * 10 : undefined,
|
||||
discountLimitPrice:
|
||||
form.value.discountLimitPrice !== undefined ? form.value.discountLimitPrice * 100 : undefined,
|
||||
usePrice: form.value.usePrice !== undefined ? form.value.usePrice * 100 : undefined,
|
||||
validStartTime:
|
||||
form.value.validTimes && form.value.validTimes.length === 2
|
||||
? form.value.validTimes[0]
|
||||
: undefined,
|
||||
validEndTime:
|
||||
form.value.validTimes && form.value.validTimes.length === 2
|
||||
? form.value.validTimes[1]
|
||||
: undefined
|
||||
}
|
||||
|
||||
// 修改的提交
|
||||
if (form.value.id != null) {
|
||||
try {
|
||||
await updateCouponTemplate(data)
|
||||
message.success('修改成功')
|
||||
open.value = false
|
||||
getList()
|
||||
} catch {}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await createCouponTemplate(data)
|
||||
message.success('新增成功')
|
||||
open.value = false
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 优惠劵模板状态修改 */
|
||||
const handleStatusChange = async (row: any) => {
|
||||
// 此时,row 已经变成目标状态了,所以可以直接提交请求和提示
|
||||
let text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
|
||||
|
||||
try {
|
||||
await message.confirm('确认要"' + text + '""' + row.name + '"优惠劵吗?')
|
||||
await updateCouponTemplateStatus(row.id, row.status)
|
||||
message.success(text + '成功')
|
||||
} catch {
|
||||
// 异常时,需要将 row.status 状态重置回之前的
|
||||
row.status =
|
||||
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: any) => {
|
||||
const id = row.id
|
||||
try {
|
||||
await message.confirm('是否确认删除优惠劵编号为"' + id + '"的数据项?')
|
||||
await deleteCouponTemplate(id)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// 格式化【优惠金额/折扣】
|
||||
const discountFormat = (row: any) => {
|
||||
if (row.discountType === PromotionDiscountTypeEnum.PRICE.type) {
|
||||
return `¥${(row.discountPrice / 100.0).toFixed(2)}`
|
||||
}
|
||||
if (row.discountType === PromotionDiscountTypeEnum.PERCENT.type) {
|
||||
return `¥${(row.discountPrice / 100.0).toFixed(2)}`
|
||||
}
|
||||
return '未知【' + row.discountType + '】'
|
||||
}
|
||||
|
||||
// 格式化【领取上限】
|
||||
const takeLimitCountFormat = (row: any) => {
|
||||
if (row.takeLimitCount === -1) {
|
||||
return '无领取限制'
|
||||
}
|
||||
return `${row.takeLimitCount} 张/人`
|
||||
}
|
||||
|
||||
// 格式化【有效期限】
|
||||
const validityTypeFormat = (row: any) => {
|
||||
if (row.validityType === CouponTemplateValidityTypeEnum.DATE.type) {
|
||||
return `${formatDate(row.validStartTime)} 至 ${formatDate(row.validEndTime)}`
|
||||
}
|
||||
if (row.validityType === CouponTemplateValidityTypeEnum.TERM.type) {
|
||||
return `领取后第 ${row.fixedStartTerm} - ${row.fixedEndTerm} 天内可用`
|
||||
}
|
||||
return '未知【' + row.validityType + '】'
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,160 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="65%">
|
||||
<Form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:isCol="true"
|
||||
:rules="rules"
|
||||
:schema="allSchemas.formSchema"
|
||||
>
|
||||
<!-- 先选择 -->
|
||||
<template #spuIds>
|
||||
<el-button @click="spuSelectRef.open()">选择商品</el-button>
|
||||
<SpuAndSkuList
|
||||
ref="spuAndSkuListRef"
|
||||
:rule-config="ruleConfig"
|
||||
:spu-list="spuList"
|
||||
:spu-property-list-p="spuPropertyList"
|
||||
/>
|
||||
</template>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
<SpuSelect ref="spuSelectRef" @confirm="selectSpu" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { SpuAndSkuList, SpuProperty, SpuSelect } from '../../components'
|
||||
import { allSchemas, rules } from './seckillActivity.data'
|
||||
|
||||
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
|
||||
import { getPropertyList, RuleConfig } from '@/views/mall/product/spu/components'
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
|
||||
defineOptions({ name: 'PromotionSeckillActivityForm' })
|
||||
|
||||
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 formRef = ref() // 表单 Ref
|
||||
const spuSelectRef = ref() // 商品和属性选择 Ref
|
||||
const spuAndSkuListRef = ref() // sku 秒杀配置组件Ref
|
||||
const ruleConfig: RuleConfig[] = [
|
||||
{
|
||||
name: 'productConfig.stock',
|
||||
rule: (arg) => arg > 1,
|
||||
message: '商品秒杀库存必须大于 1 !!!'
|
||||
},
|
||||
{
|
||||
name: 'productConfig.seckillPrice',
|
||||
rule: (arg) => arg > 0.01,
|
||||
message: '商品秒杀价格必须大于 0.01 !!!'
|
||||
}
|
||||
]
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据 TODO 没测试估计有问题
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await SeckillActivityApi.getSeckillActivity(id)
|
||||
formRef.value.setValues(data)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
const spuList = ref<SeckillActivityApi.SpuExtension[]>([]) // 选择的 spu
|
||||
const spuPropertyList = ref<SpuProperty<SeckillActivityApi.SpuExtension>[]>([])
|
||||
const selectSpu = (spuIds: number[]) => {
|
||||
formRef.value.setValues({ spuIds })
|
||||
getSpuDetails(spuIds)
|
||||
}
|
||||
/**
|
||||
* 获取 SPU 详情
|
||||
* TODO 获取 SPU 详情,放到各自活动表单来做,让 SpuAndSkuList 职责单一点
|
||||
* @param spuIds
|
||||
*/
|
||||
const getSpuDetails = async (spuIds: number[]) => {
|
||||
const spuProperties: SpuProperty<SeckillActivityApi.SpuExtension>[] = []
|
||||
spuList.value = []
|
||||
// TODO puhui999: 考虑后端添加通过 spuIds 批量获取
|
||||
for (const spuId of spuIds) {
|
||||
// 获取 SPU 详情
|
||||
const res = (await ProductSpuApi.getSpu(spuId)) as SeckillActivityApi.SpuExtension
|
||||
if (!res) {
|
||||
continue
|
||||
}
|
||||
spuList.value.push(res)
|
||||
// 初始化每个 sku 秒杀配置
|
||||
res.skus?.forEach((sku) => {
|
||||
const config: SeckillActivityApi.SeckillProductVO = {
|
||||
spuId,
|
||||
skuId: sku.id!,
|
||||
stock: 0,
|
||||
seckillPrice: 0
|
||||
}
|
||||
sku.productConfig = config
|
||||
})
|
||||
spuProperties.push({ spuId, spuDetail: res, propertyList: getPropertyList(res) })
|
||||
}
|
||||
spuPropertyList.value = spuProperties
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
spuList.value = []
|
||||
spuPropertyList.value = []
|
||||
formRef.value.getElFormRef().resetFields()
|
||||
}
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.getElFormRef().validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formRef.value.formModel as SeckillActivityApi.SeckillActivityVO
|
||||
data.spuIds = spuList.value.map((spu) => spu.id!)
|
||||
data.products = spuAndSkuListRef.value.getSkuConfigs('productConfig')
|
||||
if (formType.value === 'create') {
|
||||
await SeckillActivityApi.createSeckillActivity(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await SeckillActivityApi.updateSeckillActivity(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.demo-table-expand {
|
||||
padding-left: 42px;
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
width: 82px;
|
||||
font-weight: bold;
|
||||
color: #99a9bf;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams">
|
||||
<!-- 新增等操作按钮 -->
|
||||
<template #actionMore>
|
||||
<el-button
|
||||
v-hasPermi="['promotion:seckill-activity:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
</template>
|
||||
</Search>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<Table
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
:columns="allSchemas.tableColumns"
|
||||
:data="tableObject.tableList"
|
||||
:expand="true"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
@expand-change="expandChange"
|
||||
>
|
||||
<template #expand> 展示活动商品和商品相关属性活动配置</template>
|
||||
<template #configIds="{ row }">
|
||||
<el-tag v-for="(name, index) in convertSeckillConfigNames(row)" :key="index" class="mr-5px">
|
||||
{{ name }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
v-hasPermi="['promotion:seckill-activity:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['promotion:seckill-activity:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<SeckillActivityForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { allSchemas } from './seckillActivity.data'
|
||||
import { getListAllSimple } from '@/api/mall/promotion/seckill/seckillConfig'
|
||||
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
|
||||
import SeckillActivityForm from './SeckillActivityForm.vue'
|
||||
|
||||
defineOptions({ name: 'PromotionSeckillActivity' })
|
||||
|
||||
// tableObject:表格的属性对象,可获得分页大小、条数等属性
|
||||
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
|
||||
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const { tableObject, tableMethods } = useTable({
|
||||
getListApi: SeckillActivityApi.getSeckillActivityPage, // 分页接口
|
||||
delListApi: SeckillActivityApi.deleteSeckillActivity // 删除接口
|
||||
})
|
||||
// 获得表格的各种操作
|
||||
const { getList, setSearchParams } = tableMethods
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = (id: number) => {
|
||||
tableMethods.delList(id, false)
|
||||
}
|
||||
|
||||
// TODO @puhui:是不是直接叫 configList 就好啦
|
||||
const seckillConfigAllSimple = ref([]) // 时段配置精简列表
|
||||
const convertSeckillConfigNames = computed(
|
||||
() => (row) =>
|
||||
seckillConfigAllSimple.value
|
||||
?.filter((item) => row.configIds.includes(item.id))
|
||||
?.map((config) => config.name)
|
||||
)
|
||||
|
||||
const expandChange = (row, expandedRows) => {
|
||||
// TODO puhui:等 CRUD 完事后弄
|
||||
console.log(row, expandedRows)
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
seckillConfigAllSimple.value = await getListAllSimple()
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,262 @@
|
|||
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
|
||||
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
|
||||
import { getListAllSimple } from '@/api/mall/promotion/seckill/seckillConfig'
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
spuId: [required],
|
||||
name: [required],
|
||||
startTime: [required],
|
||||
endTime: [required],
|
||||
sort: [required],
|
||||
configIds: [required],
|
||||
totalLimitCount: [required],
|
||||
singleLimitCount: [required],
|
||||
totalStock: [required]
|
||||
})
|
||||
|
||||
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '秒杀活动名称',
|
||||
field: 'name',
|
||||
isSearch: true,
|
||||
form: {
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '活动开始时间',
|
||||
field: 'startTime',
|
||||
formatter: dateFormatter2,
|
||||
isSearch: true,
|
||||
search: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
type: 'daterange'
|
||||
}
|
||||
},
|
||||
form: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'date',
|
||||
valueFormat: 'x'
|
||||
}
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '活动结束时间',
|
||||
field: 'endTime',
|
||||
formatter: dateFormatter2,
|
||||
isSearch: true,
|
||||
search: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
type: 'daterange'
|
||||
}
|
||||
},
|
||||
form: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'date',
|
||||
valueFormat: 'x'
|
||||
}
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '秒杀时段',
|
||||
field: 'configIds',
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
multiple: true,
|
||||
optionsAlias: {
|
||||
labelField: 'name',
|
||||
valueField: 'id'
|
||||
}
|
||||
},
|
||||
api: getListAllSimple
|
||||
},
|
||||
table: {
|
||||
width: 300
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '新增订单数',
|
||||
field: 'orderCount',
|
||||
isForm: false,
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '付款人数',
|
||||
field: 'userCount',
|
||||
isForm: false,
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '订单实付金额',
|
||||
field: 'totalPrice',
|
||||
isForm: false,
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '总限购数量',
|
||||
field: 'totalLimitCount',
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '单次限够数量',
|
||||
field: 'singleLimitCount',
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '秒杀库存',
|
||||
field: 'stock',
|
||||
isForm: false,
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '秒杀总库存',
|
||||
field: 'totalStock',
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '秒杀活动商品',
|
||||
field: 'spuIds',
|
||||
isTable: false,
|
||||
isSearch: false,
|
||||
form: {
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
table: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
field: 'createTime',
|
||||
formatter: dateFormatter,
|
||||
search: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
type: 'daterange',
|
||||
defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
|
||||
}
|
||||
},
|
||||
isForm: false,
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '排序',
|
||||
field: 'sort',
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isForm: false,
|
||||
isSearch: true,
|
||||
form: {
|
||||
component: 'Radio'
|
||||
},
|
||||
table: {
|
||||
width: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
field: 'remark',
|
||||
isSearch: false,
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
table: {
|
||||
width: 300
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '操作',
|
||||
field: 'action',
|
||||
isForm: false,
|
||||
table: {
|
||||
width: 120,
|
||||
fixed: 'right'
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<Form ref="formRef" v-loading="formLoading" :rules="rules" :schema="allSchemas.formSchema" />
|
||||
<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="SeckillConfigForm" setup>
|
||||
import * as SeckillConfigApi from '@/api/mall/promotion/seckill/seckillConfig'
|
||||
import { allSchemas, rules } from './seckillConfig.data'
|
||||
|
||||
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 formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await SeckillConfigApi.getSeckillConfig(id)
|
||||
formRef.value.setValues(data)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.getElFormRef().validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formRef.value.formModel as SeckillConfigApi.SeckillConfigVO
|
||||
if (formType.value === 'create') {
|
||||
await SeckillConfigApi.createSeckillConfig(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await SeckillConfigApi.updateSeckillConfig(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,123 @@
|
|||
<template>
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams">
|
||||
<!-- 新增等操作按钮 -->
|
||||
<template #actionMore>
|
||||
<el-button
|
||||
v-hasPermi="['promotion:seckill-config:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
</template>
|
||||
</Search>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<Table
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
:columns="allSchemas.tableColumns"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
>
|
||||
<template #picUrl="{ row }">
|
||||
<el-image :src="row.picUrl" class="w-30px h-30px" @click="imagePreview(row.picUrl)" />
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
@change="handleStatusChange(row)"
|
||||
/>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
v-hasPermi="['promotion:seckill-config:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['promotion:seckill-config:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<SeckillConfigForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" name="PromotionSeckillConfig" setup>
|
||||
import { allSchemas } from './seckillConfig.data'
|
||||
import * as SeckillConfigApi from '@/api/mall/promotion/seckill/seckillConfig'
|
||||
import SeckillConfigForm from './SeckillConfigForm.vue'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
// tableObject:表格的属性对象,可获得分页大小、条数等属性
|
||||
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
|
||||
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const { tableObject, tableMethods } = useTable({
|
||||
getListApi: SeckillConfigApi.getSeckillConfigPage, // 分页接口
|
||||
delListApi: SeckillConfigApi.deleteSeckillConfig // 删除接口
|
||||
})
|
||||
// 获得表格的各种操作
|
||||
const { getList, setSearchParams } = tableMethods
|
||||
|
||||
/** 商品图预览 */
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
createImageViewer({
|
||||
urlList: [imgUrl]
|
||||
})
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = (id: number) => {
|
||||
tableMethods.delList(id, false)
|
||||
}
|
||||
|
||||
/** 修改用户状态 */
|
||||
const handleStatusChange = async (row: SeckillConfigApi.SeckillConfigVO) => {
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
|
||||
await message.confirm('确认要"' + text + '""' + row.name + '?')
|
||||
// 发起修改状态
|
||||
await SeckillConfigApi.updateSeckillConfigStatus(row.id, row.status)
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {
|
||||
// 取消后,进行恢复按钮
|
||||
row.status =
|
||||
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
|
||||
}
|
||||
}
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,79 @@
|
|||
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
startTime: [required],
|
||||
endTime: [required],
|
||||
picUrl: [required],
|
||||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '秒杀时段名称',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
label: '开始时间点',
|
||||
field: 'startTime',
|
||||
isSearch: false,
|
||||
search: {
|
||||
component: 'TimePicker'
|
||||
},
|
||||
form: {
|
||||
component: 'TimePicker',
|
||||
componentProps: {
|
||||
valueFormat: 'HH:mm:ss'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '结束时间点',
|
||||
field: 'endTime',
|
||||
isSearch: false,
|
||||
search: {
|
||||
component: 'TimePicker'
|
||||
},
|
||||
form: {
|
||||
component: 'TimePicker',
|
||||
componentProps: {
|
||||
valueFormat: 'HH:mm:ss'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '秒杀主图',
|
||||
field: 'picUrl',
|
||||
isSearch: false,
|
||||
form: {
|
||||
component: 'UploadImg'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true,
|
||||
form: {
|
||||
component: 'Radio'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
field: 'createTime',
|
||||
isForm: false,
|
||||
isSearch: false,
|
||||
formatter: dateFormatter
|
||||
},
|
||||
{
|
||||
label: '操作',
|
||||
field: 'action',
|
||||
isForm: false
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
|
@ -89,7 +89,7 @@
|
|||
<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
|
||||
|
@ -171,7 +171,10 @@ const formRules = reactive({
|
|||
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const areaCache = ref([]) //由于区域节点懒加载,已选区域节点需要缓存展示
|
||||
const areaCache = ref([]) // 由于区域节点懒加载,已选区域节点需要缓存展示
|
||||
// TODO @jason:配送的时候,只允许选择省市级别,不允许选择区;如果这样的话,是不是打开弹窗,直接把城市都请求过来;
|
||||
// TODO @jaosn:因为只有省市两级,感觉就不用特殊做全国逻辑;选择全国,就默认把子节点都选择上;另外,选择父节点,要把子节点选中哈;
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
|
@ -204,9 +207,9 @@ const open = async (type: string, id?: number) => {
|
|||
}
|
||||
item.freePrice = fenToYuan(item.freePrice)
|
||||
})
|
||||
//已选的区域节点
|
||||
// 已选的区域节点
|
||||
const areaIds = chargeAreaIds.concat(freeAreaIds)
|
||||
//区域节点,懒加载方式。 已选节点需要缓存展示
|
||||
// 区域节点,懒加载方式。已选节点需要缓存展示
|
||||
areaCache.value = await getAreaListByIds(areaIds.join(','))
|
||||
}
|
||||
} finally {
|
||||
|
@ -226,8 +229,9 @@ const submitForm = async () => {
|
|||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as DeliveryExpressTemplateApi.DeliveryExpressTemplateVO
|
||||
// 前端价格以元展示,提交到后端。用分计算
|
||||
// TODO @jason:不能直接这样改,要复制出来改。不然后端操作失败,数据已经被改了
|
||||
data.templateCharge.forEach((item) => {
|
||||
//前端价格以元展示,提交到后端。用分计算
|
||||
item.startPrice = yuanToFen(item.startPrice)
|
||||
item.extraPrice = yuanToFen(item.extraPrice)
|
||||
})
|
||||
|
@ -248,6 +252,7 @@ const submitForm = async () => {
|
|||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
|
@ -269,6 +274,7 @@ const resetForm = () => {
|
|||
columnTitle.value = columnTitleMap.get(1)
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 配送计费方法改变 */
|
||||
const changeChargeMode = (chargeMode: number) => {
|
||||
columnTitle.value = columnTitleMap.get(chargeMode)
|
||||
|
@ -276,6 +282,24 @@ const changeChargeMode = (chargeMode: number) => {
|
|||
const defaultArea = [{ id: 1, name: '全国', disabled: false }]
|
||||
|
||||
/** 初始化数据 */
|
||||
// TODO @jason:是不是不用写这样一个初始化方法,columnTitleMap 直接就可以了呀
|
||||
// const columnTitleMap = {
|
||||
// '1': {
|
||||
// startCountTitle: '首件',
|
||||
// extraCountTitle: '续件',
|
||||
// freeCountTitle: '包邮件数'
|
||||
// },
|
||||
// '2': {
|
||||
// startCountTitle: '首件重量(kg)',
|
||||
// extraCountTitle: '续件重量(kg)',
|
||||
// freeCountTitle: '包邮重量(kg)'
|
||||
// },
|
||||
// '3': {
|
||||
// startCountTitle: '首件体积(m³)',
|
||||
// extraCountTitle: '续件体积(m³)',
|
||||
// freeCountTitle: '包邮体积(m³)'
|
||||
// }
|
||||
// }
|
||||
const initData = async () => {
|
||||
// TODO 从服务端全量加载数据, 后面看懒加载是不是可以从前端获取数据。 目前从后端获取数据
|
||||
// formLoading.value = true
|
||||
|
@ -286,7 +310,7 @@ const initData = async () => {
|
|||
// } finally {
|
||||
// formLoading.value = false
|
||||
// }
|
||||
//表头标题和计费方式的映射
|
||||
// 表头标题和计费方式的映射
|
||||
columnTitleMap.set(1, {
|
||||
startCountTitle: '首件',
|
||||
extraCountTitle: '续件',
|
||||
|
@ -320,6 +344,7 @@ const loadChargeArea = async (node, resolve) => {
|
|||
const item = data[0]
|
||||
if (areaIds.includes(item.id)) {
|
||||
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
|
||||
// TODO @jason:先不做这个功能哈。
|
||||
//item.disabled = true
|
||||
}
|
||||
resolve(data)
|
||||
|
@ -357,10 +382,11 @@ const loadFreeArea = async (node, resolve) => {
|
|||
} else {
|
||||
const id = node.data.id
|
||||
const data = await getChildrenArea(id)
|
||||
//已选区域需要禁止再次选择
|
||||
// 已选区域需要禁止再次选择
|
||||
data.forEach((item) => {
|
||||
if (areaIds.includes(item.id)) {
|
||||
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
|
||||
// TODO @jason:先不做这个功能哈。
|
||||
//item.disabled = true
|
||||
}
|
||||
})
|
||||
|
@ -378,11 +404,13 @@ const addChargeArea = () => {
|
|||
extraPrice: 1
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除计费区域 */
|
||||
const deleteChargeArea = (index) => {
|
||||
const data = formData.value
|
||||
data.templateCharge.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 添加包邮区域 */
|
||||
const addFreeArea = () => {
|
||||
const data = formData.value
|
||||
|
@ -392,6 +420,7 @@ const addFreeArea = () => {
|
|||
freePrice: 1
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除包邮区域 */
|
||||
const deleteFreeArea = (index) => {
|
||||
const data = formData.value
|
||||
|
|
|
@ -112,6 +112,7 @@ const queryParams = reactive({
|
|||
chargeMode: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="60%">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<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-col>
|
||||
<el-col :span="12">
|
||||
<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-col>
|
||||
</el-row>
|
||||
<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">
|
||||
<el-form-item label="门店手机" prop="phone">
|
||||
<el-input v-model="formData.phone" placeholder="请输入门店手机" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="门店简介" prop="introduction">
|
||||
<el-input
|
||||
v-model="formData.introduction"
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
placeholder="请输入门店简介"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="门店所在地区" prop="areaId">
|
||||
<el-cascader v-model="formData.areaId" :options="areaList" :props="areaTreeProps" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="门店详细地址" prop="detailAddress">
|
||||
<el-input v-model="formData.detailAddress" placeholder="请输入门店详细地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="营业开始时间" prop="openingTime">
|
||||
<el-time-select
|
||||
v-model="formData.openingTime"
|
||||
:max-time="formData.closingTime"
|
||||
placeholder="开始时间"
|
||||
start="08:30"
|
||||
step="00:15"
|
||||
end="23:30"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="营业结束时间" prop="closingTime">
|
||||
<el-time-select
|
||||
v-model="formData.closingTime"
|
||||
:min-time="formData.openingTime"
|
||||
placeholder="结束时间"
|
||||
start="08:30"
|
||||
step="00:15"
|
||||
end="23:30"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="经度" prop="longitude">
|
||||
<el-input v-model="formData.longitude" placeholder="请输入门店经度" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="纬度" prop="latitude">
|
||||
<el-input v-model="formData.latitude" placeholder="请输入门店纬度" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="获取经纬度">
|
||||
<el-button type="primary" @click="mapDialogVisible.value = true">获取</el-button>
|
||||
</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>
|
||||
<el-dialog
|
||||
v-model="mapDialogVisible"
|
||||
title="获取经纬度"
|
||||
append-to-body
|
||||
width="500px"
|
||||
class="mapBox"
|
||||
>
|
||||
<iframe id="mapPage" width="100%" height="100%" frameborder="0" :src="tencentLbsUrl"></iframe>
|
||||
</el-dialog>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as DeliveryPickUpStoreApi from '@/api/mall/trade/delivery/pickUpStore'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import { getAreaTree } from '@/api/system/area'
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const mapDialogVisible = ref(false) // 地图弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: '',
|
||||
phone: '',
|
||||
logo: '',
|
||||
detailAddress: '',
|
||||
introduction: '',
|
||||
areaId: 0,
|
||||
openingTime: undefined,
|
||||
closingTime: undefined,
|
||||
latitude: undefined,
|
||||
longitude: undefined,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '门店名称不能为空', trigger: 'blur' }],
|
||||
logo: [{ required: true, message: '门店 logo 不能为空', trigger: 'blur' }],
|
||||
phone: [
|
||||
{ required: true, message: '门店手机不能为空', trigger: 'blur' },
|
||||
{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
],
|
||||
areaId: [{ required: true, message: '门店所在区域不能为空', trigger: 'blur' }],
|
||||
detailAddress: [{ required: true, message: '门店详细地址不能为空', trigger: 'blur' }],
|
||||
openingTime: [{ required: true, message: '营业开始时间不能为空', trigger: 'blur' }],
|
||||
closingTime: [{ required: true, message: '营业结束时间不能为空', trigger: 'blur' }],
|
||||
latitude: [{ required: true, message: '纬度不能为空', trigger: 'blur' }],
|
||||
longitude: [{ required: true, message: '经度不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const areaTreeProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
emitPath: false
|
||||
}
|
||||
const areaList = ref() // 区域树
|
||||
const tencentLbsUrl = ref('') // 腾讯位置服务 url
|
||||
|
||||
/** 打开弹窗 */
|
||||
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 DeliveryPickUpStoreApi.getDeliveryPickUpStore(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 DeliveryPickUpStoreApi.DeliveryPickUpStoreVO
|
||||
if (formType.value === 'create') {
|
||||
await DeliveryPickUpStoreApi.createDeliveryPickUpStore(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await DeliveryPickUpStoreApi.updateDeliveryPickUpStore(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
phone: '',
|
||||
logo: '',
|
||||
detailAddress: '',
|
||||
introduction: '',
|
||||
areaId: undefined,
|
||||
openingTime: undefined,
|
||||
closingTime: undefined,
|
||||
latitude: undefined,
|
||||
longitude: undefined,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 选择经纬度 */
|
||||
const selectAddress = function (loc: any): void {
|
||||
if (loc.latlng && loc.latlng.lat) {
|
||||
formData.value.latitude = loc.latlng.lat
|
||||
}
|
||||
if (loc.latlng && loc.latlng.lng) {
|
||||
formData.value.longitude = loc.latlng.lng
|
||||
}
|
||||
mapDialogVisible.value = false
|
||||
}
|
||||
|
||||
/** 初始化数据 */
|
||||
const initData = async () => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await getAreaTree()
|
||||
areaList.value = data
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
// TODO @jason:要不创建一个 initTencentLbsMap
|
||||
window.selectAddress = selectAddress
|
||||
window.addEventListener(
|
||||
'message',
|
||||
function (event) {
|
||||
// 接收位置信息,用户选择确认位置点后选点组件会触发该事件,回传用户的位置信息
|
||||
let loc = event.data
|
||||
if (loc && loc.module === 'locationPicker') {
|
||||
// 防止其他应用也会向该页面 post 信息,需判断 module 是否为 'locationPicker'
|
||||
window.parent.selectAddress(loc)
|
||||
}
|
||||
},
|
||||
false
|
||||
)
|
||||
const data = await ConfigApi.getConfigKey('tencent.lbs.key')
|
||||
let key = ''
|
||||
if (data && data.length > 0) {
|
||||
key = data
|
||||
}
|
||||
tencentLbsUrl.value = `https://apis.map.qq.com/tools/locpicker?type=1&key=${key}&referer=myapp`
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
initData()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.mapBox .el-dialog__body {
|
||||
height: 640px !important;
|
||||
}
|
||||
</style>
|
|
@ -1,32 +1,54 @@
|
|||
<template>
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="是否开启" prop="tradeDeductEnable">
|
||||
<el-select
|
||||
v-model="queryParams.tradeDeductEnable"
|
||||
placeholder="请选择是否开启"
|
||||
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
|
||||
<el-form-item label="门店手机" prop="phone">
|
||||
<el-input
|
||||
v-model="queryParams.phone"
|
||||
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 label="门店状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="门店状态" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in options"
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</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="datetimerange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
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" @click="openForm('create')" v-hasPermi="['point:config:create']">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['trade:delivery:pick-up-store:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
|
@ -34,7 +56,7 @@
|
|||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['point:config:export']"
|
||||
v-hasPermi="['trade:delivery:pick-up-store:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
|
@ -45,26 +67,25 @@
|
|||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="序号" align="center" prop="id" />
|
||||
<el-table-column
|
||||
label="积分抵扣(是否开启)"
|
||||
align="center"
|
||||
prop="tradeDeductEnable"
|
||||
:formatter="tradeDeductFormat"
|
||||
/>
|
||||
<el-table-column label="抵扣单位(元)" align="center" prop="tradeDeductUnitPrice" />
|
||||
<el-table-column label="积分抵扣最大值" align="center" prop="tradeDeductMaxPrice" />
|
||||
<el-table-column label="1元赠送多少分" align="center" prop="tradeGivePoint" />
|
||||
<el-table-column label="编号" prop="id" />
|
||||
<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="门店名称" prop="name" />
|
||||
<el-table-column label="门店手机" prop="phone" />
|
||||
<el-table-column label="门店详细地址" align="center" prop="detailAddress" />
|
||||
<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"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="变更时间"
|
||||
align="center"
|
||||
prop="updateTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
|
@ -73,7 +94,7 @@
|
|||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['point:config:update']"
|
||||
v-hasPermi="['trade:delivery:pick-up-store:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
|
@ -81,67 +102,64 @@
|
|||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['point:config:delete']"
|
||||
v-hasPermi="['trade:delivery:pick-up-store:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ConfigForm ref="formRef" @success="getList" />
|
||||
<DeliveryPickUpStoreForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
<script setup lang="ts" name="DeliveryPickUpStore">
|
||||
import * as DeliveryPickUpStoreApi from '@/api/mall/trade/delivery/pickUpStore'
|
||||
import DeliveryPickUpStoreForm from './PickUpStoreForm.vue'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as ConfigApi from '@/api/point/config'
|
||||
import ConfigForm from './ConfigForm.vue'
|
||||
|
||||
defineOptions({ name: 'PointConfig' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const list = ref<any[]>([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
tradeDeductEnable: null
|
||||
status: undefined,
|
||||
phone: undefined,
|
||||
name: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: '1',
|
||||
label: '是'
|
||||
},
|
||||
{
|
||||
value: '0',
|
||||
label: '否'
|
||||
}
|
||||
]
|
||||
|
||||
const tradeDeductFormat = (row, column, cellValue) => {
|
||||
return cellValue === 1 ? '是' : '否'
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await DeliveryPickUpStoreApi.deleteDeliveryPickUpStore(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ConfigApi.getConfigPage(queryParams)
|
||||
const data = await DeliveryPickUpStoreApi.getDeliveryPickUpStorePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
|
@ -161,25 +179,6 @@ const resetQuery = () => {
|
|||
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 ConfigApi.deleteConfig(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
|
@ -187,8 +186,8 @@ const handleExport = async () => {
|
|||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await ConfigApi.exportConfig(queryParams)
|
||||
download.excel(data, '积分设置.xls')
|
||||
const data = await DeliveryPickUpStoreApi.exportDeliveryPickUpStoreApi(queryParams)
|
||||
download.excel(data, '自提门店.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
|
@ -0,0 +1,572 @@
|
|||
<template>
|
||||
<!-- 搜索 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
:inline="true"
|
||||
>
|
||||
<el-form-item label="订单状态" prop="status">
|
||||
<el-select class="!w-280px" v-model="queryParams.status" clearable placeholder="全部">
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.TRADE_ORDER_STATUS)"
|
||||
:key="(dict.value as string)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="支付方式" prop="payChannelCode">
|
||||
<el-select
|
||||
v-model="queryParams.payChannelCode"
|
||||
class="!w-280px"
|
||||
clearable
|
||||
placeholder="全部"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.PAY_CHANNEL_CODE_TYPE)"
|
||||
:key="(dict.value as string)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</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-280px"
|
||||
start-placeholder="自定义时间"
|
||||
end-placeholder="自定义时间"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="订单来源" prop="terminal">
|
||||
<el-select class="!w-280px" v-model="queryParams.terminal" clearable placeholder="全部">
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.TERMINAL)"
|
||||
:key="(dict.value as string)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="订单类型" prop="type">
|
||||
<el-select class="!w-280px" v-model="queryParams.type" clearable placeholder="全部">
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.TRADE_ORDER_TYPE)"
|
||||
:key="(dict.value as string)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="订单搜索">
|
||||
<el-input
|
||||
v-show="true"
|
||||
class="!w-280px"
|
||||
v-model="queryType.v"
|
||||
clearable
|
||||
placeholder="请输入"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-select style="width: 110px" v-model="queryType.k" clearable placeholder="全部">
|
||||
<el-option
|
||||
v-for="dict in searchList"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery" v-hasPermi="['trade:order:query']">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery" v-hasPermi="['trade:order:query']">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button type="success" plain @click="handleExport" :loading="exportLoading">
|
||||
<!-- v-hasPermi="['trade:order:export']" -->
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出TODO
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
<!-- 表格 -->
|
||||
<ContentWrap>
|
||||
<!-- 表单 -->
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column type="expand" fixed="left">
|
||||
<template #default="scope">
|
||||
<el-descriptions class="mx-40">
|
||||
<el-descriptions-item label="商品原价(总): ">{{
|
||||
'¥ ' +
|
||||
parseFloat((scope.row.originalPrice / 100) as unknown as string).toFixed(2) +
|
||||
' 元'
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="下单时间: ">
|
||||
{{ formatDate(scope.row.createTime) }}</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item label="推广人: ">TODO</el-descriptions-item>
|
||||
<el-descriptions-item label="用户备注: ">{{
|
||||
scope.row.userRemark
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="商家备注: ">{{ scope.row.remark }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="100" fixed="left">
|
||||
<template #header>
|
||||
<el-dropdown icon="eq:search" @command="handleDropType">
|
||||
<el-button link type="primary">全选({{ orderSelect.selectTotal }}) </el-button>
|
||||
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="1">当前页</el-dropdown-item>
|
||||
<el-dropdown-item command="2">所有页</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.itemSelect" @change="handcheckclick(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="订单号" align="center" min-width="110">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="showOrderDetail(scope.row)">{{
|
||||
scope.row.no
|
||||
}}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单类型" align="center" min-width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.TRADE_ORDER_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用户信息" align="center" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="goUserDetail(scope.row)"
|
||||
>{{ scope.row.userId }}{{ '[' + scope.row.user.nickname + ']' }}</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
min-width="180"
|
||||
/>
|
||||
<el-table-column label="订单来源" align="center" min-width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.terminal"
|
||||
:type="DICT_TYPE.TERMINAL"
|
||||
:value="scope.row.terminal"
|
||||
/>
|
||||
<span v-else>{{ scope.terminal }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="商品信息" align="left" min-width="200" prop="items">
|
||||
<template #default="scope">
|
||||
<el-popover
|
||||
ref="popover"
|
||||
placement="bottom"
|
||||
:title="'订单:' + scope.row.no"
|
||||
:width="400"
|
||||
trigger="hover"
|
||||
>
|
||||
<template #reference>
|
||||
<div>
|
||||
<div v-for="item in scope.row.items" :key="item">
|
||||
<el-image
|
||||
style="width: 36px; height: 36px"
|
||||
:src="item.picUrl"
|
||||
:preview-src-list="[item.picUrl]"
|
||||
fit="cover"
|
||||
@click="imagePreview(item.picUrl)"
|
||||
/>
|
||||
<span class="m-2">{{ item.spuName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-for="item in scope.row.items" :key="item">
|
||||
<div>
|
||||
<p>{{ item.spuName }}</p>
|
||||
<!-- TODO xiaobai: 是不是 (item.payPrice / 100.0).toFixed(2) -->
|
||||
<p>{{
|
||||
'¥ ' +
|
||||
parseFloat((item.payPrice / 100) as unknown as string).toFixed(2) +
|
||||
'元 x ' +
|
||||
item.count
|
||||
}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际支付(元)" align="center" prop="payPrice" min-width="100">
|
||||
<template #default="scope">
|
||||
{{ '¥ ' + parseFloat((scope.row.payPrice / 100) as unknown as string).toFixed(2) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="支付时间"
|
||||
prop="payTime"
|
||||
min-width="180"
|
||||
/>
|
||||
<el-table-column label="支付类型" align="center" min-width="100" prop="payChannelCode">
|
||||
<template #default="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.payChannelCode"
|
||||
:type="DICT_TYPE.PAY_CHANNEL_CODE_TYPE"
|
||||
:value="scope.row.payChannelCode"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单状态" align="center" prop="status" min-width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== ''"
|
||||
:type="DICT_TYPE.TRADE_ORDER_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
<span v-else>{{ scope.status }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" min-width="150">
|
||||
<template #default="scope">
|
||||
<!-- <el-button v-if="scope.row.status == '0'" link type="primary" @click="sendXX(scope.row)"
|
||||
>待支付</el-button> -->
|
||||
<el-button v-if="scope.row.status == '10'" link type="primary" @click="sendXX(scope.row)"
|
||||
>发货</el-button
|
||||
>
|
||||
<el-button link type="primary" @click="showOrderDetail(scope.row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
<el-image-viewer
|
||||
v-if="imgViewVisible"
|
||||
:url-list="imageViewerList"
|
||||
@close="imgViewVisible = false"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts" name="OrderList">
|
||||
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
|
||||
import * as TradeOrderApi from '@/api/mall/trade/order'
|
||||
import {
|
||||
TradeOrderPageReqVO,
|
||||
SelectType,
|
||||
TradeOrderPageItemRespVO
|
||||
} from '@/api/mall/trade/order/type/orderType'
|
||||
import { dateFormatter, formatDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
|
||||
const message = useMessage()
|
||||
const { push } = useRouter()
|
||||
const imgViewVisible = ref(false) // 商品图预览
|
||||
const imageViewerList = ref<string[]>([]) // 商品图预览列表
|
||||
const queryFormRef = ref()
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const total = ref(0) // 总记录数
|
||||
const list = ref<Array<TradeOrderPageItemRespVO | any>>([]) //表数据
|
||||
|
||||
// 选中状态选中处理
|
||||
const orderSelect: SelectType = reactive({
|
||||
queryParams: {} as TradeOrderPageReqVO,
|
||||
selectTotal: 0,
|
||||
selectAllFlag: false,
|
||||
selectData: new Map<number, Set<string>>(),
|
||||
unSelectList: new Set<string>()
|
||||
})
|
||||
|
||||
//表单搜索
|
||||
const queryParams: TradeOrderPageReqVO = reactive({
|
||||
pageNo: 1, //首页
|
||||
pageSize: 10 //页面大小
|
||||
})
|
||||
|
||||
const queryType = reactive({ k: '', v: '' }) // 订单搜索类型kv
|
||||
|
||||
/*
|
||||
* 订单搜索
|
||||
* 商品名称 商品件数 全部 需要后端支持TODO
|
||||
*/
|
||||
const searchList = ref([
|
||||
{ value: 'no', label: '订单号' },
|
||||
{ value: 'userId', label: '用户UID' },
|
||||
{ value: 'userNickname', label: '用户昵称' },
|
||||
{ value: 'userMobile', label: '用户电话' },
|
||||
{ value: 'spuName', label: '商品名称TODO' },
|
||||
{ value: 'itemCount', label: '商品件数TODO' }
|
||||
])
|
||||
|
||||
/**
|
||||
|
||||
当前页/? 如果pageNo存在,则将但前数据全部按照单个选中模式取消 ,不存在,则新增全页 增加 Map.pageNo Map.roderNoList
|
||||
单个选中 如果pagelist存在,订单号选中状态取反,并对总数按选中状态加减。如果pagelist不存在,订单号选中状态取反,并对总数按选中状态加减,增加 Map.pageNo,
|
||||
如果当前Map.pageNo 所对应list 为空 ,清除pageNo
|
||||
* @param command ===1 当前页 选中 ===2 所有页面选中
|
||||
*/
|
||||
const handleDropType = (command: string) => {
|
||||
let i = 0
|
||||
//当前页按钮
|
||||
if (command === '1') {
|
||||
//如果该页面有选中数据 则选中事件触发时 取消该页面
|
||||
if (orderSelect.selectData && orderSelect.selectData.has(queryParams.pageNo)) {
|
||||
for (i = 0; i < list.value.length; i++) {
|
||||
if (orderSelect.selectData.get(queryParams.pageNo)!.has(list.value[i].id)) {
|
||||
//选中数量减少
|
||||
orderSelect.selectTotal -= 1
|
||||
//考虑全选中,针对某一页面选中当前页时 会将所有数据中去掉该页面, 需要登记到 orderSelect.unSelectList
|
||||
unSelectListRecord(list.value[i].id, 'add')
|
||||
}
|
||||
list.value[i]['itemSelect'] = false
|
||||
}
|
||||
orderSelect.selectData.delete(queryParams.pageNo) //移除该页面
|
||||
} else {
|
||||
//当前页选中状态中 默认全选中
|
||||
orderSelect.selectData.set(queryParams.pageNo, new Set<string>())
|
||||
for (i = 0; i < list.value.length; i++) {
|
||||
list.value[i]['itemSelect'] = true
|
||||
orderSelect.selectData.get(queryParams.pageNo)!.add(list.value[i].id)
|
||||
//选中数量增加
|
||||
orderSelect.selectTotal += 1
|
||||
//对于登记过取消状态中的数据排除
|
||||
unSelectListRecord(list.value[i].id, 'del')
|
||||
}
|
||||
}
|
||||
}
|
||||
//所有页按钮
|
||||
if (command === '2') {
|
||||
orderSelect.selectAllFlag = !orderSelect.selectAllFlag
|
||||
|
||||
if (orderSelect.selectAllFlag) {
|
||||
//打勾勾 //全选
|
||||
orderSelect.selectData?.set(queryParams.pageNo, new Set<string>())
|
||||
for (i = 0; i < list.value.length; i++) {
|
||||
list.value[i]['itemSelect'] = true
|
||||
orderSelect.selectData?.get(queryParams.pageNo)?.add(list.value[i].id) //id是主键不重复
|
||||
}
|
||||
orderSelect.selectTotal = total.value
|
||||
} else {
|
||||
//取消勾勾
|
||||
for (i; i < list.value.length; i++) {
|
||||
list.value[i]['itemSelect'] = false
|
||||
}
|
||||
initSelect() //重置之前选中的类容清空
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//对全选状态中的 单选或者当前页面单选时登记取消的数据
|
||||
const unSelectListRecord = (id: string, op: string) => {
|
||||
if (!orderSelect.selectAllFlag) {
|
||||
return
|
||||
}
|
||||
if (op == 'add') {
|
||||
orderSelect.unSelectList.add(id)
|
||||
} else {
|
||||
orderSelect.unSelectList.delete(id)
|
||||
}
|
||||
}
|
||||
/***复选框选中 */
|
||||
const handcheckclick = (row: any) => {
|
||||
if (row.itemSelect) {
|
||||
orderSelect.selectTotal += 1
|
||||
if (!orderSelect.selectData.has(queryParams.pageNo)) {
|
||||
orderSelect.selectData?.set(queryParams.pageNo, new Set<string>())
|
||||
}
|
||||
orderSelect.selectData?.get(queryParams.pageNo)?.add(row.id)
|
||||
unSelectListRecord(row.id, 'del')
|
||||
} else {
|
||||
orderSelect.selectTotal -= 1
|
||||
orderSelect.selectData.get(queryParams.pageNo)?.delete(row.id)
|
||||
unSelectListRecord(row.id, 'add')
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 导出数据
|
||||
*/
|
||||
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
//增加查询条件 用于全选时后台查询数据
|
||||
orderSelect.queryParams = queryParams
|
||||
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
//全选时 根据上送的条件查询所有数据,在排除unseleectList 数据,
|
||||
//非全选时, 根据上送的selectData 直接查询数据 后台实现导出数据接口即可
|
||||
console.log(orderSelect)
|
||||
download.excel(orderSelect as any, '订单信息.xls') //?
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
//TODO
|
||||
exportLoading.value = false
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
queryType.v = '' //重置
|
||||
queryType.k = ''
|
||||
//休眠0.1s 等待watch响应
|
||||
setTimeout(() => {
|
||||
initSelect() //重置对选中设置恢复初始状态
|
||||
handleQuery()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
/**选中状态初始化**/
|
||||
const initSelect = () => {
|
||||
orderSelect.queryParams = {} as TradeOrderPageReqVO
|
||||
orderSelect.selectTotal = 0
|
||||
orderSelect.selectAllFlag = false
|
||||
orderSelect.selectData?.clear()
|
||||
orderSelect.unSelectList?.clear()
|
||||
}
|
||||
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await TradeOrderApi.getOrderList(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
let i = 0
|
||||
if (orderSelect.selectData && orderSelect.selectData.has(queryParams.pageNo)) {
|
||||
//该页面已经加载过了。直接按照之前状态设置选中状态值
|
||||
for (i = 0; i < list.value.length; i++) {
|
||||
if (orderSelect.selectData.get(queryParams.pageNo)!.has(list.value[i].id)) {
|
||||
list.value[i]['itemSelect'] = true //之前已经选取过了
|
||||
} else {
|
||||
list.value[i]['itemSelect'] = false
|
||||
}
|
||||
}
|
||||
} else if (orderSelect.selectAllFlag) {
|
||||
//全选状态中 首次加载页面 默认全部选中
|
||||
orderSelect.selectData.set(queryParams.pageNo, new Set<string>())
|
||||
for (i = 0; i < list.value.length; i++) {
|
||||
list.value[i]['itemSelect'] = true
|
||||
orderSelect.selectData.get(queryParams.pageNo)!.add(list.value[i].id)
|
||||
}
|
||||
} else {
|
||||
//非全选状态中 首次加载默认非选中状态
|
||||
for (i; i < list.value.length; i++) {
|
||||
list.value[i]['itemSelect'] = false //设置状态为未选中状态
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转订单详情
|
||||
*/
|
||||
const showOrderDetail = (row: any) => {
|
||||
push({ name: 'TradeOrderDetail', query: { id: row.id } })
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转用户详情
|
||||
*/
|
||||
const goUserDetail = (row: any) => {
|
||||
console.log('TODO User Detail: ' + row.userId)
|
||||
}
|
||||
/**
|
||||
* 发货
|
||||
*/
|
||||
const sendXX = (row: any) => {
|
||||
console.log('TODO Send XX: ' + row.no)
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品图预览
|
||||
* @param imgUrl
|
||||
*/
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
imageViewerList.value = [imgUrl]
|
||||
imgViewVisible.value = true
|
||||
}
|
||||
|
||||
//针对订单搜索类型和值进行调整 使用监听器
|
||||
watch(
|
||||
() => [queryType.k, queryType.v],
|
||||
([newK, newV], [oldK]) => {
|
||||
//重置oldK对应得value
|
||||
if (oldK != newK) {
|
||||
if (oldK == 'no' && queryParams.no != '') {
|
||||
queryParams.no = ''
|
||||
} else if (oldK == 'userId' && queryParams.userId != '') {
|
||||
queryParams.userId = ''
|
||||
} else if (oldK == 'userNickname' && queryParams.userNickname != '') {
|
||||
queryParams.userNickname = ''
|
||||
} else if (oldK == 'userMobile' && queryParams.userMobile !== '') {
|
||||
queryParams.userMobile = ''
|
||||
} else if (oldK == 'spuName' && queryParams.spuName !== '') {
|
||||
queryParams.spuName = ''
|
||||
} else if (oldK == 'itemCount' && queryParams.itemCount !== '') {
|
||||
queryParams.itemCount = ''
|
||||
} else if (oldK == '' && queryParams.all !== '') {
|
||||
queryParams.all = ''
|
||||
}
|
||||
}
|
||||
// 根据选中得k设置Value
|
||||
if (newK == 'no') {
|
||||
queryParams.no = newV
|
||||
} else if (newK == 'userId') {
|
||||
queryParams.userId = newV
|
||||
} else if (newK == 'userNickname') {
|
||||
queryParams.userNickname = newV
|
||||
} else if (newK == 'userMobile') {
|
||||
queryParams.userMobile = newV
|
||||
} else if (newK == 'spuName') {
|
||||
queryParams.spuName = newV
|
||||
} else if (newK == 'itemCount') {
|
||||
queryParams.itemCount = newV
|
||||
} else if (newK == '') {
|
||||
queryParams.all = newV
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
initSelect()
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,365 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 订单信息 -->
|
||||
<el-descriptions title="订单信息">
|
||||
<el-descriptions-item label="订单号: ">{{ order.no }}</el-descriptions-item>
|
||||
<el-descriptions-item label="配送方式: ">物流配送</el-descriptions-item>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-descriptions-item label="营销活动: ">物流配送</el-descriptions-item>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-descriptions-item label="订单类型: ">
|
||||
<dict-tag :type="DICT_TYPE.TRADE_ORDER_TYPE" :value="order.type" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="收货人: ">{{ order.receiverName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="买家留言: ">{{ order.userRemark }}</el-descriptions-item>
|
||||
<el-descriptions-item label="订单来源: ">
|
||||
<dict-tag :type="DICT_TYPE.TERMINAL" :value="order.terminal" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话: ">{{ order.receiverMobile }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商家备注: ">{{ order.remark }}</el-descriptions-item>
|
||||
<el-descriptions-item label="支付单号: ">{{ order.payOrderId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="付款方式: ">
|
||||
<dict-tag :type="DICT_TYPE.PAY_CHANNEL_CODE_TYPE" :value="order.payChannelCode" />
|
||||
</el-descriptions-item>
|
||||
<!-- <el-descriptions-item label="买家: ">{{ order.user.nickname }}</el-descriptions-item> -->
|
||||
<!-- TODO 芋艿:待实现:跳转会员 -->
|
||||
<el-descriptions-item label="收货地址: ">
|
||||
{{ order.receiverAreaName }} {{ order.receiverDetailAddress }}
|
||||
<el-link
|
||||
v-clipboard:copy="order.receiverAreaName + ' ' + order.receiverDetailAddress"
|
||||
v-clipboard:success="clipboardSuccess"
|
||||
icon="ep:document-copy"
|
||||
type="primary"
|
||||
/>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 订单状态 -->
|
||||
<el-descriptions title="订单状态" :column="1">
|
||||
<el-descriptions-item label="订单状态: ">
|
||||
<!-- TODO xiaobai:status 一定有值哈,不用判断 -->
|
||||
<dict-tag
|
||||
v-if="order.status !== ''"
|
||||
:type="DICT_TYPE.TRADE_ORDER_STATUS"
|
||||
:value="order.status"
|
||||
/>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label-class-name="no-colon">
|
||||
<el-button type="primary" size="small">调整价格</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">备注</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">发货</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">关闭订单</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">修改地址</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">打印电子面单</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">打印发货单</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">确认收货</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label><span style="color: red">提醒: </span></template>
|
||||
买家付款成功后,货款将直接进入您的商户号(微信、支付宝)<br />
|
||||
请及时关注你发出的包裹状态,确保可以配送至买家手中 <br />
|
||||
如果买家表示没收到货或货物有问题,请及时联系买家处理,友好协商
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 物流信息 TODO -->
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<el-descriptions title="商品信息">
|
||||
<el-descriptions-item labelClassName="no-colon">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="15">
|
||||
<el-table :data="order.items" border>
|
||||
<el-table-column prop="spuName" label="商品" width="auto">
|
||||
<template #default="{ row }">
|
||||
{{ row.spuName }}
|
||||
<el-tag
|
||||
size="medium"
|
||||
v-for="property in row.properties"
|
||||
:key="property.propertyId"
|
||||
>
|
||||
{{ property.propertyName }}: {{ property.valueName }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price" label="商品原价(元)" width="150">
|
||||
<template #default="{ row }"> ¥{{ (row.price / 100.0).toFixed(2) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="count" label="数量" width="100" />
|
||||
<el-table-column prop="payPrice" label="合计(元)" width="150">
|
||||
<template #default="{ row }"> ¥{{ (row.payPrice / 100.0).toFixed(2) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="afterSaleStatus" label="售后状态" width="auto">
|
||||
<template #default="{ row }">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.TRADE_ORDER_ITEM_AFTER_SALE_STATUS"
|
||||
:value="row.afterSaleStatus"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
<el-col :span="10" />
|
||||
</el-row>
|
||||
</el-descriptions-item>
|
||||
<!-- 占位 -->
|
||||
<!-- <el-descriptions-item v-for="item in 5" label-class-name="no-colon" :key="item" /> -->
|
||||
</el-descriptions>
|
||||
<el-descriptions column="6">
|
||||
<el-descriptions-item label="商品总额: ">
|
||||
<!-- TODO xiaobai: 是不是 (item.payPrice / 100.0).toFixed(2) -->
|
||||
¥{{ parseFloat((order.totalPrice / 100.0) as unknown as string).toFixed(2) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运费金额: ">
|
||||
¥{{ parseFloat((order.deliveryPrice / 100.0) as unknown as string).toFixed(2) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="订单调价: ">
|
||||
¥{{
|
||||
parseFloat((order.adjustPrice / 100.0) as unknown as string).toFixed(2)
|
||||
}}</el-descriptions-item
|
||||
>
|
||||
|
||||
<el-descriptions-item>
|
||||
<template #label><span style="color: red">商品优惠: </span></template>
|
||||
<!-- 没理解TODO order.totalPrice - order.totalPrice -->
|
||||
¥{{
|
||||
parseFloat(((order.totalPrice - order.totalPrice) / 100.0) as unknown as string).toFixed(
|
||||
2
|
||||
)
|
||||
}}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label><span style="color: red">订单优惠: </span></template>
|
||||
¥{{ parseFloat((order.discountPrice / 100.0) as unknown as string).toFixed(2) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label><span style="color: red">积分抵扣: </span></template>
|
||||
¥{{ parseFloat((order.pointPrice / 100.0) as unknown as string).toFixed(2) }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item v-for="item in 5" label-class-name="no-colon" :key="item" />
|
||||
<!-- 占位 -->
|
||||
<el-descriptions-item label="应付金额: ">
|
||||
¥{{ (order.payPrice / 100.0).toFixed(2) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- TODO 芋艿:需要改改 -->
|
||||
<div v-for="group in detailGroups" :key="group.title">
|
||||
<el-descriptions v-bind="group.groupProps" :title="group.title">
|
||||
<!-- 订单操作日志 -->
|
||||
<el-descriptions-item v-if="group.key === 'orderLog'" labelClassName="no-colon">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="activity in detailInfo[group.key]"
|
||||
:key="activity.timestamp"
|
||||
:timestamp="activity.timestamp"
|
||||
>
|
||||
{{ activity.content }}
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-descriptions-item>
|
||||
|
||||
<!-- 物流信息 -->
|
||||
<!-- TODO @xiaobai:改成一个包裹哈;目前只允许发货一次 -->
|
||||
<el-descriptions-item v-if="group.key === 'expressInfo'" labelClassName="no-colon">
|
||||
<!-- 循环包裹物流信息 -->
|
||||
<div v-show="(pkgInfo = detailInfo[group.key]) !== null" style="border: 1px dashed">
|
||||
<!-- 包裹详情 -->
|
||||
<el-descriptions class="m-5">
|
||||
<el-descriptions-item
|
||||
v-for="(pkgChild, pkgCIdx) in group.children"
|
||||
v-bind="pkgChild.childProps"
|
||||
:key="`pkgChild_${pkgCIdx}`"
|
||||
:label="pkgChild.label"
|
||||
>
|
||||
<!-- 包裹商品列表 -->
|
||||
<template v-if="pkgChild.valueKey === 'goodsList' && pkgInfo[pkgChild.valueKey]">
|
||||
<div
|
||||
v-for="(goodInfo, goodInfoIdx) in pkgInfo[pkgChild.valueKey]"
|
||||
:key="`goodInfo_${goodInfoIdx}`"
|
||||
style="display: flex"
|
||||
>
|
||||
<el-image
|
||||
style="width: 100px; height: 100px; flex: none"
|
||||
:src="goodInfo.imgUrl"
|
||||
/>
|
||||
<el-descriptions :column="1">
|
||||
<el-descriptions-item labelClassName="no-colon">{{
|
||||
goodInfo.name
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="数量">{{ goodInfo.count }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 包裹物流详情 -->
|
||||
<template v-else-if="pkgChild.valueKey === 'wlxq'">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="6" :offset="1">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(activity, index) in pkgInfo[pkgChild.valueKey]"
|
||||
:key="index"
|
||||
:timestamp="activity.timestamp"
|
||||
>
|
||||
{{ activity.content }}
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ pkgInfo[pkgChild.valueKey] }}
|
||||
</template>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" name="TradeOrderDetail" setup>
|
||||
// TODO @xiaobai:在 order 下创建一个 order/detail,然后改名为 index.vue
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import * as TradeOrderApi from '@/api/mall/trade/order'
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const { query } = useRoute()
|
||||
const queryParams = reactive({
|
||||
id: query.id
|
||||
})
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const order = ref<any>({
|
||||
items: [],
|
||||
user: {}
|
||||
}) // 详情数据
|
||||
|
||||
const detailGroups = ref([
|
||||
{
|
||||
title: '物流信息',
|
||||
key: 'expressInfo',
|
||||
children: [
|
||||
{ label: '发货时间: ', valueKey: 'fhsj' },
|
||||
{ label: '物流公司: ', valueKey: 'wlgs' },
|
||||
{ label: '运单号: ', valueKey: 'ydh' },
|
||||
{ label: '物流状态: ', valueKey: 'wlzt', childProps: { span: 3 } },
|
||||
{ label: '物流详情: ', valueKey: 'wlxq' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '订单操作日志',
|
||||
key: 'orderLog'
|
||||
}
|
||||
])
|
||||
|
||||
const detailInfo = ref({
|
||||
expressInfo:
|
||||
// 物流信息
|
||||
{
|
||||
label: '包裹1',
|
||||
name: 'bg1',
|
||||
fhsj: '2022-11-03 16:50:45',
|
||||
wlgs: '极兔',
|
||||
ydh: '2132123',
|
||||
wlzt: '不支持此快递公司',
|
||||
wlxq: [
|
||||
{
|
||||
content: '正在派送途中,请您准备签收(派件人:王涛,电话:13854563814)',
|
||||
timestamp: '2018-04-15 15:00:16'
|
||||
},
|
||||
{
|
||||
content: '快件到达 【烟台龙口东江村委营业点】',
|
||||
timestamp: '2018-04-13 14:54:19'
|
||||
},
|
||||
{
|
||||
content: '快件已发车',
|
||||
timestamp: '2018-04-11 12:55:52'
|
||||
},
|
||||
{
|
||||
content: '快件已发车',
|
||||
timestamp: '2018-04-11 12:55:52'
|
||||
},
|
||||
{
|
||||
content: '快件已发车',
|
||||
timestamp: '2018-04-11 12:55:52'
|
||||
}
|
||||
]
|
||||
},
|
||||
orderLog: [
|
||||
// 订单操作日志
|
||||
{
|
||||
content: '买家【乌鸦】关闭了订单',
|
||||
timestamp: '2018-04-15 15:00:16'
|
||||
},
|
||||
{
|
||||
content: '买家【乌鸦】下单了',
|
||||
timestamp: '2018-04-15 15:00:16'
|
||||
}
|
||||
],
|
||||
goodsInfo: [] // 商品详情tableData
|
||||
})
|
||||
// 暂考虑一次性加载详情页面所有数据 TODO
|
||||
const getlist = async () => {
|
||||
dialogVisible.value = true
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await TradeOrderApi.getOrderDetail(queryParams.id as unknown as number)
|
||||
order.value = res
|
||||
console.log(order)
|
||||
} catch {
|
||||
message.error('获取详情数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getlist()
|
||||
})
|
||||
const clipboardSuccess = () => {
|
||||
message.success('复制成功')
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-descriptions) {
|
||||
&:not(:nth-child(1)) {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.el-descriptions__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
width: 3px;
|
||||
height: 20px;
|
||||
background-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-descriptions-item__container {
|
||||
margin: 0 10px;
|
||||
|
||||
.no-colon {
|
||||
margin: 0;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="hideId" v-show="false">
|
||||
<el-input v-model="formData.id" />
|
||||
</el-form-item>
|
||||
<!-- TODO @xiaqing:展示给用户的字段名,可以和 crmeb 保持一直,然后每一个表单都有类似 crmeb 的 tip;例如说:积分抵用比例(1积分抵多少金额)单位:元 -->
|
||||
<el-form-item label="积分抵扣" prop="tradeDeductEnable">
|
||||
<el-switch v-model="formData.tradeDeductEnable" />
|
||||
</el-form-item>
|
||||
<!-- TODO @xiaqing:用户看到的是元,最多 2 位;分是后端的存储哈 -->
|
||||
<el-form-item label="抵扣单位(分)" prop="tradeDeductUnitPrice">
|
||||
<el-input-number
|
||||
v-model="formData.tradeDeductUnitPrice"
|
||||
placeholder="请输入抵扣单位(分)"
|
||||
style="width: 300px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="积分抵扣最大值" prop="tradeDeductMaxPrice">
|
||||
<el-input-number
|
||||
v-model="formData.tradeDeductMaxPrice"
|
||||
placeholder="请输入积分抵扣最大值"
|
||||
style="width: 300px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="1 元赠送多少分" prop="tradeGivePoint">
|
||||
<el-input-number
|
||||
v-model="formData.tradeGivePoint"
|
||||
placeholder="请输入 1 元赠送多少积分"
|
||||
style="width: 300px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit">提交</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/point/config'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
tradeDeductEnable: undefined,
|
||||
tradeDeductUnitPrice: undefined,
|
||||
tradeDeductMaxPrice: undefined,
|
||||
tradeGivePoint: undefined
|
||||
})
|
||||
const formRules = reactive({})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 修改积分配置 */
|
||||
const onSubmit = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as ConfigApi.ConfigVO
|
||||
await ConfigApi.saveConfig(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 获得积分配置 */
|
||||
const getConfig = async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfig()
|
||||
formData.value = data
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getConfig()
|
||||
})
|
||||
</script>
|
|
@ -13,7 +13,7 @@
|
|||
<el-form-item label="业务类型" prop="bizType">
|
||||
<el-select v-model="formData.bizType" placeholder="请选择业务类型">
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.POINT_BIZ_TYPE)"
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.MEMBER_POINT_BIZ_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
|
@ -41,7 +41,7 @@
|
|||
<el-form-item label="积分状态" prop="status">
|
||||
<el-select v-model="formData.status" placeholder="积分状态">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.POINT_STATUS)"
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.MEMBER_POINT_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
|
@ -157,6 +157,7 @@ const submitForm = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO @xiaqing:不需要更新操作哇?
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
|
@ -25,7 +25,7 @@
|
|||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.POINT_BIZ_TYPE)"
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.MEMBER_POINT_BIZ_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
|
@ -50,14 +50,14 @@
|
|||
<el-form-item label="积分状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.POINT_STATUS)"
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.MEMBER_POINT_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发生时间" prop="createDate">
|
||||
<el-form-item label="获得时间" prop="createDate">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createDate"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
|
@ -71,18 +71,6 @@
|
|||
<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" @click="openForm('create')" v-hasPermi="['point:record:create']">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['point:record:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
@ -90,13 +78,18 @@
|
|||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="序号" align="center" prop="id" />
|
||||
<el-table-column label="业务编码" align="center" prop="bizId" />
|
||||
<el-table-column label="业务类型" align="center" prop="bizType">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.POINT_BIZ_TYPE" :value="scope.row.bizType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<!-- TODO @xiaqing:展示用户的昵称哈; -->
|
||||
<el-table-column label="用户" align="center" prop="userId" />
|
||||
<el-table-column label="积分标题" align="center" prop="title" />
|
||||
<el-table-column label="积分描述" align="center" prop="description" />
|
||||
<el-table-column
|
||||
label="获得时间"
|
||||
align="center"
|
||||
prop="createDate"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<!-- todo @xiaqing:可以参考 crmeb 的展示,把积分和增加减少放一起,用红色和绿色展示 -->
|
||||
<el-table-column
|
||||
label="操作类型"
|
||||
align="center"
|
||||
|
@ -107,16 +100,19 @@
|
|||
}
|
||||
"
|
||||
/>
|
||||
<el-table-column label="积分标题" align="center" prop="title" />
|
||||
<el-table-column label="积分描述" align="center" prop="description" />
|
||||
<el-table-column label="积分" align="center" prop="point" />
|
||||
<el-table-column label="变动后的积分" align="center" prop="totalPoint" />
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<el-table-column label="业务编码" align="center" prop="bizId" />
|
||||
<el-table-column label="业务类型" align="center" prop="bizType">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.POINT_STATUS" :value="scope.row.status" />
|
||||
<dict-tag :type="DICT_TYPE.MEMBER_POINT_BIZ_TYPE" :value="scope.row.bizType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.MEMBER_POINT_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用户id" align="center" prop="userId" />
|
||||
<el-table-column
|
||||
label="冻结时间"
|
||||
align="center"
|
||||
|
@ -129,32 +125,6 @@
|
|||
prop="thawingTime"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="发生时间"
|
||||
align="center"
|
||||
prop="createDate"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['point:record:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['point:record:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
|
@ -172,15 +142,11 @@
|
|||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getStrDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as RecordApi from '@/api/point/record'
|
||||
import RecordForm from './RecordForm.vue'
|
||||
|
||||
defineOptions({ name: 'PointRecord' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
|
@ -195,7 +161,6 @@ const queryParams = reactive({
|
|||
createDate: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
|
@ -221,40 +186,6 @@ const resetQuery = () => {
|
|||
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 RecordApi.deleteRecord(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await RecordApi.exportRecord(queryParams)
|
||||
download.excel(data, '用户积分记录.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
|
@ -8,6 +8,7 @@
|
|||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<!-- TODO @xiaqing:搜索可以去掉,因为一共就没几条配置哈 -->
|
||||
<el-form-item label="签到天数" prop="day">
|
||||
<el-input
|
||||
v-model="queryParams.day"
|
||||
|
@ -35,6 +36,7 @@
|
|||
:loading="exportLoading"
|
||||
v-hasPermi="['point:sign-in-config:export']"
|
||||
>
|
||||
<!-- TODO @xiaqing:四个功能的导出都可以去掉 -->
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
@ -44,15 +46,10 @@
|
|||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="序号" align="center" prop="id" v-if="false" />
|
||||
<!-- TODO @xiaqing:展示优化下,改成第 1 天、第 2 天这种 -->
|
||||
<el-table-column label="签到天数" align="center" prop="day" />
|
||||
<el-table-column label="签到分数" align="center" prop="point" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="获得积分" align="center" prop="point" />
|
||||
<!-- TODO @xiaqing:展示一个是否开启 -->
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
|
@ -88,7 +85,6 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as SignInConfigApi from '@/api/point/signInConfig'
|
||||
import SignInConfigForm from './SignInConfigForm.vue'
|
||||
|
@ -109,6 +105,7 @@ const queryParams = reactive({
|
|||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
// TODO @xiaqing:可以不分页;
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
|
@ -40,14 +40,6 @@
|
|||
<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="['point:sign-in-record:create']"-->
|
||||
<!-- >-->
|
||||
<!-- <Icon icon="ep:plus" class="mr-5px" /> 新增-->
|
||||
<!-- </el-button>-->
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
|
@ -64,10 +56,11 @@
|
|||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="序号" align="center" prop="id" />
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<!-- TODO @xiaqing:展示用户昵称 -->
|
||||
<el-table-column label="签到用户" align="center" prop="userId" />
|
||||
<el-table-column label="签到天数" align="center" prop="day" />
|
||||
<el-table-column label="签到的分数" align="center" prop="point" />
|
||||
<el-table-column label="获得积分" align="center" prop="point" />
|
||||
<el-table-column
|
||||
label="签到时间"
|
||||
align="center"
|
||||
|
@ -76,14 +69,6 @@
|
|||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<!-- <el-button-->
|
||||
<!-- link-->
|
||||
<!-- type="primary"-->
|
||||
<!-- @click="openForm('update', scope.row.id)"-->
|
||||
<!-- v-hasPermi="['point:sign-in-record:update']"-->
|
||||
<!-- >-->
|
||||
<!-- 编辑-->
|
||||
<!-- </el-button>-->
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
|
@ -1,122 +0,0 @@
|
|||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" style="width: 600px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="积分抵扣" prop="tradeDeductEnable">
|
||||
<el-select v-model="formData.tradeDeductEnable" placeholder="请选择是否开启">
|
||||
<el-option
|
||||
v-for="dict in options"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="抵扣单位(元)" prop="tradeDeductUnitPrice">
|
||||
<el-input v-model="formData.tradeDeductUnitPrice" placeholder="请输入抵扣单位(元)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="积分抵扣最大值" prop="tradeDeductMaxPrice">
|
||||
<el-input v-model="formData.tradeDeductMaxPrice" placeholder="请输入积分抵扣最大值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="1元赠送多少分" prop="tradeGivePoint">
|
||||
<el-input v-model="formData.tradeGivePoint" placeholder="请输入1元赠送多少分" />
|
||||
</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 lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/point/config'
|
||||
|
||||
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,
|
||||
tradeDeductEnable: undefined,
|
||||
tradeDeductUnitPrice: undefined,
|
||||
tradeDeductMaxPrice: undefined,
|
||||
tradeGivePoint: undefined
|
||||
})
|
||||
const formRules = reactive({})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: '1',
|
||||
label: '是'
|
||||
},
|
||||
{
|
||||
value: '0',
|
||||
label: '否'
|
||||
}
|
||||
]
|
||||
|
||||
/** 打开弹窗 */
|
||||
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 ConfigApi.getConfig(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 unknown as ConfigApi.ConfigVO
|
||||
if (formType.value === 'create') {
|
||||
await ConfigApi.createConfig(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ConfigApi.updateConfig(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
tradeDeductEnable: undefined,
|
||||
tradeDeductUnitPrice: undefined,
|
||||
tradeDeductMaxPrice: undefined,
|
||||
tradeGivePoint: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -3,7 +3,7 @@
|
|||
<Descriptions :data="detailData" :schema="allSchemas.detailSchema">
|
||||
<!-- 展示 HTML 内容 -->
|
||||
<template #templateContent="{ row }">
|
||||
<div v-html="row.templateContent"></div>
|
||||
<div v-dompurify-html="row.templateContent"></div>
|
||||
</template>
|
||||
</Descriptions>
|
||||
</Dialog>
|
||||
|
|
Loading…
Reference in New Issue