add trade order 20230606
This commit is contained in:
commit
e2d795f49d
4
.env.dev
4
.env.dev
@ -19,13 +19,13 @@ VITE_API_URL=/admin-api
|
|||||||
VITE_BASE_PATH=/
|
VITE_BASE_PATH=/
|
||||||
|
|
||||||
# 是否删除debugger
|
# 是否删除debugger
|
||||||
VITE_DROP_DEBUGGER=false
|
VITE_DROP_DEBUGGER=true
|
||||||
|
|
||||||
# 是否删除console.log
|
# 是否删除console.log
|
||||||
VITE_DROP_CONSOLE=false
|
VITE_DROP_CONSOLE=false
|
||||||
|
|
||||||
# 是否sourcemap
|
# 是否sourcemap
|
||||||
VITE_SOURCEMAP=true
|
VITE_SOURCEMAP=false
|
||||||
|
|
||||||
# 输出路径
|
# 输出路径
|
||||||
VITE_OUT_DIR=dist-dev
|
VITE_OUT_DIR=dist-dev
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
"url": "^0.11.0",
|
"url": "^0.11.0",
|
||||||
"video.js": "^8.3.0",
|
"video.js": "^8.3.0",
|
||||||
"vue": "3.3.4",
|
"vue": "3.3.4",
|
||||||
|
"vue-dompurify-html": "^4.1.4",
|
||||||
"vue-i18n": "9.2.2",
|
"vue-i18n": "9.2.2",
|
||||||
"vue-router": "^4.2.1",
|
"vue-router": "^4.2.1",
|
||||||
"vue-types": "^5.0.3",
|
"vue-types": "^5.0.3",
|
||||||
|
@ -7,8 +7,7 @@ export interface Property {
|
|||||||
valueName?: string // 属性值名称
|
valueName?: string // 属性值名称
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO puhui999:是不是直接叫 Sku 更简洁一点哈。type 待后面,总感觉有个类型?
|
export interface Sku {
|
||||||
export interface SkuType {
|
|
||||||
id?: number // 商品 SKU 编号
|
id?: number // 商品 SKU 编号
|
||||||
spuId?: number // SPU 编号
|
spuId?: number // SPU 编号
|
||||||
properties?: Property[] // 属性数组
|
properties?: Property[] // 属性数组
|
||||||
@ -25,8 +24,7 @@ export interface SkuType {
|
|||||||
salesCount?: number // 商品销量
|
salesCount?: number // 商品销量
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO puhui999:是不是直接叫 Spu 更简洁一点哈。type 待后面,总感觉有个类型?
|
export interface Spu {
|
||||||
export interface SpuType {
|
|
||||||
id?: number
|
id?: number
|
||||||
name?: string // 商品名称
|
name?: string // 商品名称
|
||||||
categoryId?: number | null // 商品分类
|
categoryId?: number | null // 商品分类
|
||||||
@ -39,9 +37,9 @@ export interface SpuType {
|
|||||||
brandId?: number | null // 商品品牌编号
|
brandId?: number | null // 商品品牌编号
|
||||||
specType?: boolean // 商品规格
|
specType?: boolean // 商品规格
|
||||||
subCommissionType?: boolean // 分销类型
|
subCommissionType?: boolean // 分销类型
|
||||||
skus: SkuType[] // sku数组
|
skus?: Sku[] // sku数组
|
||||||
description?: string // 商品详情
|
description?: string // 商品详情
|
||||||
sort?: string // 商品排序
|
sort?: number // 商品排序
|
||||||
giveIntegral?: number // 赠送积分
|
giveIntegral?: number // 赠送积分
|
||||||
virtualSalesCount?: number // 虚拟销量
|
virtualSalesCount?: number // 虚拟销量
|
||||||
recommendHot?: boolean // 是否热卖
|
recommendHot?: boolean // 是否热卖
|
||||||
@ -62,12 +60,12 @@ export const getTabsCount = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建商品 Spu
|
// 创建商品 Spu
|
||||||
export const createSpu = (data: SpuType) => {
|
export const createSpu = (data: Spu) => {
|
||||||
return request.post({ url: '/product/spu/create', data })
|
return request.post({ url: '/product/spu/create', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新商品 Spu
|
// 更新商品 Spu
|
||||||
export const updateSpu = (data: SpuType) => {
|
export const updateSpu = (data: Spu) => {
|
||||||
return request.put({ url: '/product/spu/update', data })
|
return request.put({ url: '/product/spu/update', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,3 +88,8 @@ export const deleteSpu = (id: number) => {
|
|||||||
export const exportSpu = async (params) => {
|
export const exportSpu = async (params) => {
|
||||||
return await request.download({ url: '/product/spu/export', params })
|
return await request.download({ url: '/product/spu/export', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获得商品 SPU 精简列表
|
||||||
|
export const getSpuSimpleList = async () => {
|
||||||
|
return request.get({ url: '/product/spu/get-simple-list' })
|
||||||
|
}
|
||||||
|
18
src/api/mall/promotion/coupon.ts
Executable file
18
src/api/mall/promotion/coupon.ts
Executable file
@ -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
|
||||||
|
})
|
||||||
|
}
|
83
src/api/mall/promotion/couponTemplate.ts
Executable file
83
src/api/mall/promotion/couponTemplate.ts
Executable file
@ -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'
|
||||||
|
})
|
||||||
|
}
|
@ -33,6 +33,11 @@ export const getDeliveryExpressTemplate = async (id: number) => {
|
|||||||
return await request.get({ url: '/trade/delivery/express-template/get?id=' + id })
|
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) => {
|
export const createDeliveryExpressTemplate = async (data: DeliveryExpressTemplateVO) => {
|
||||||
return await request.post({ url: '/trade/delivery/express-template/create', data })
|
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) => {
|
export const deleteDeliveryExpressTemplate = async (id: number) => {
|
||||||
return await request.delete({ url: '/trade/delivery/express-template/delete?id=' + id })
|
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 })
|
|
||||||
}
|
|
||||||
|
46
src/api/mall/trade/delivery/pickUpStore/index.ts
Normal file
46
src/api/mall/trade/delivery/pickUpStore/index.ts
Normal file
@ -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 })
|
||||||
|
}
|
@ -1,16 +1,16 @@
|
|||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
|
import { computed, defineComponent, onMounted, PropType, ref, unref, watch } from 'vue'
|
||||||
import { ElForm, ElFormItem, ElRow, ElCol, ElTooltip } from 'element-plus'
|
import { ElCol, ElForm, ElFormItem, ElRow, ElTooltip } from 'element-plus'
|
||||||
import { componentMap } from './componentMap'
|
import { componentMap } from './componentMap'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { getSlot } from '@/utils/tsxHelper'
|
import { getSlot } from '@/utils/tsxHelper'
|
||||||
import {
|
import {
|
||||||
setTextPlaceholder,
|
|
||||||
setGridProp,
|
|
||||||
setComponentProps,
|
|
||||||
setItemComponentSlots,
|
|
||||||
initModel,
|
initModel,
|
||||||
setFormItemSlots
|
setComponentProps,
|
||||||
|
setFormItemSlots,
|
||||||
|
setGridProp,
|
||||||
|
setItemComponentSlots,
|
||||||
|
setTextPlaceholder
|
||||||
} from './helper'
|
} from './helper'
|
||||||
import { useRenderSelect } from './components/useRenderSelect'
|
import { useRenderSelect } from './components/useRenderSelect'
|
||||||
import { useRenderRadio } from './components/useRenderRadio'
|
import { useRenderRadio } from './components/useRenderRadio'
|
||||||
@ -196,7 +196,7 @@ export default defineComponent({
|
|||||||
<span>{item.label}</span>
|
<span>{item.label}</span>
|
||||||
<ElTooltip placement="right" raw-content>
|
<ElTooltip placement="right" raw-content>
|
||||||
{{
|
{{
|
||||||
content: () => <span v-html={item.labelMessage}></span>,
|
content: () => <span v-dompurify-html={item.labelMessage}></span>,
|
||||||
default: () => (
|
default: () => (
|
||||||
<Icon
|
<Icon
|
||||||
icon="ep:warning"
|
icon="ep:warning"
|
||||||
|
@ -38,9 +38,10 @@ import App from './App.vue'
|
|||||||
import './permission'
|
import './permission'
|
||||||
|
|
||||||
import '@/plugins/tongji' // 百度统计
|
import '@/plugins/tongji' // 百度统计
|
||||||
|
|
||||||
import Logger from '@/utils/Logger'
|
import Logger from '@/utils/Logger'
|
||||||
|
|
||||||
|
import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患
|
||||||
|
|
||||||
// 创建实例
|
// 创建实例
|
||||||
const setupAll = async () => {
|
const setupAll = async () => {
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
@ -61,6 +62,8 @@ const setupAll = async () => {
|
|||||||
|
|
||||||
await router.isReady()
|
await router.isReady()
|
||||||
|
|
||||||
|
app.use(VueDOMPurifyHTML)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,6 +395,19 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
title: '编辑商品',
|
title: '编辑商品',
|
||||||
activeMenu: '/product/product-spu'
|
activeMenu: '/product/product-spu'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'productSpuDetail/: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'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -222,7 +222,7 @@ export const PayRefundStatusEnum = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 商品SPU枚举类
|
* 商品 SPU 状态
|
||||||
*/
|
*/
|
||||||
export const ProductSpuStatusEnum = {
|
export const ProductSpuStatusEnum = {
|
||||||
RECYCLE: {
|
RECYCLE: {
|
||||||
@ -238,3 +238,59 @@ export const ProductSpuStatusEnum = {
|
|||||||
name: '上架'
|
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: '折扣'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -145,12 +145,14 @@ export enum DICT_TYPE {
|
|||||||
MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型
|
MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型
|
||||||
MP_MESSAGE_TYPE = 'mp_message_type', // 消息类型
|
MP_MESSAGE_TYPE = 'mp_message_type', // 消息类型
|
||||||
|
|
||||||
// ========== MALL 模块 ==========
|
// ========== MALL - PROMOTION 模块 ==========
|
||||||
PRODUCT_UNIT = 'product_unit', // 商品单位
|
PROMOTION_DISCOUNT_TYPE = 'promotion_discount_type', // 优惠类型
|
||||||
PRODUCT_SPU_STATUS = 'product_spu_status', //商品状态
|
PROMOTION_PRODUCT_SCOPE = 'promotion_product_scope', // 营销的商品范围
|
||||||
|
PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE = 'promotion_coupon_template_validity_type', // 优惠劵模板的有限期类型
|
||||||
// ========== MALL 交易模块 ==========
|
PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态
|
||||||
EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode', //快递的计费方式
|
PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式
|
||||||
|
PROMOTION_ACTIVITY_STATUS = 'promotion_activity_status', // 优惠活动的状态
|
||||||
|
PROMOTION_CONDITION_TYPE = 'promotion_condition_type', // 营销的条件类型枚举
|
||||||
|
|
||||||
//===add by 20230530====
|
//===add by 20230530====
|
||||||
// ========== MALL - ORDER 模块 ==========
|
// ========== MALL - ORDER 模块 ==========
|
||||||
|
@ -3,6 +3,7 @@ interface TreeHelperConfig {
|
|||||||
children: string
|
children: string
|
||||||
pid: string
|
pid: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_CONFIG: TreeHelperConfig = {
|
const DEFAULT_CONFIG: TreeHelperConfig = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
children: 'children',
|
children: 'children',
|
||||||
@ -133,6 +134,7 @@ export const filter = <T = any>(
|
|||||||
): T[] => {
|
): T[] => {
|
||||||
config = getConfig(config)
|
config = getConfig(config)
|
||||||
const children = config.children as string
|
const children = config.children as string
|
||||||
|
|
||||||
function listFilter(list: T[]) {
|
function listFilter(list: T[]) {
|
||||||
return list
|
return list
|
||||||
.map((node: any) => ({ ...node }))
|
.map((node: any) => ({ ...node }))
|
||||||
@ -141,6 +143,7 @@ export const filter = <T = any>(
|
|||||||
return func(node) || (node[children] && node[children].length)
|
return func(node) || (node[children] && node[children].length)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return listFilter(tree)
|
return listFilter(tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,6 +267,7 @@ export const handleTree = (data: any[], id?: string, parentId?: string, children
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tree
|
return tree
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,3 +306,94 @@ export const handleTree2 = (data, id, parentId, children, rootId) => {
|
|||||||
})
|
})
|
||||||
return treeData !== '' ? treeData : data
|
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
|
||||||
|
}
|
||||||
|
@ -16,20 +16,20 @@
|
|||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 弹窗:表单预览 -->
|
<!-- 弹窗:表单预览 -->
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible" max-height="600">
|
<Dialog v-model="dialogVisible" :title="dialogTitle" max-height="600">
|
||||||
<div ref="editor" v-if="dialogVisible">
|
<div v-if="dialogVisible" ref="editor">
|
||||||
<el-button style="float: right" @click="copy(formData)">
|
<el-button style="float: right" @click="copy(formData)">
|
||||||
{{ t('common.copy') }}
|
{{ t('common.copy') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-scrollbar height="580">
|
<el-scrollbar height="580">
|
||||||
<div>
|
<div>
|
||||||
<pre><code class="hljs" v-html="highlightedCode(formData)"></code></pre>
|
<pre><code v-dompurify-html="highlightedCode(formData)" class="hljs"></code></pre>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" name="InfraBuild">
|
<script lang="ts" name="InfraBuild" setup>
|
||||||
import FcDesigner from '@form-create/designer'
|
import FcDesigner from '@form-create/designer'
|
||||||
import { useClipboard } from '@vueuse/core'
|
import { useClipboard } from '@vueuse/core'
|
||||||
import { isString } from '@/utils/is'
|
import { isString } from '@/utils/is'
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
{{ t('common.copy') }}
|
{{ t('common.copy') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<div>
|
<div>
|
||||||
<pre><code class="hljs" v-html="highlightedCode(item)"></code></pre>
|
<pre><code v-dompurify-html="highlightedCode(item)" class="hljs"></code></pre>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
<BasicInfoForm
|
<BasicInfoForm
|
||||||
ref="basicInfoRef"
|
ref="basicInfoRef"
|
||||||
v-model:activeName="activeName"
|
v-model:activeName="activeName"
|
||||||
|
:is-detail="isDetail"
|
||||||
:propFormData="formData"
|
:propFormData="formData"
|
||||||
/>
|
/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
@ -12,6 +13,7 @@
|
|||||||
<DescriptionForm
|
<DescriptionForm
|
||||||
ref="descriptionRef"
|
ref="descriptionRef"
|
||||||
v-model:activeName="activeName"
|
v-model:activeName="activeName"
|
||||||
|
:is-detail="isDetail"
|
||||||
:propFormData="formData"
|
:propFormData="formData"
|
||||||
/>
|
/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
@ -19,13 +21,16 @@
|
|||||||
<OtherSettingsForm
|
<OtherSettingsForm
|
||||||
ref="otherSettingsRef"
|
ref="otherSettingsRef"
|
||||||
v-model:activeName="activeName"
|
v-model:activeName="activeName"
|
||||||
|
:is-detail="isDetail"
|
||||||
:propFormData="formData"
|
:propFormData="formData"
|
||||||
/>
|
/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<el-form>
|
<el-form>
|
||||||
<el-form-item style="float: right">
|
<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-button @click="close">返回</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@ -42,16 +47,17 @@ import { convertToInteger, formatToFraction } from '@/utils'
|
|||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { push, currentRoute } = useRouter() // 路由
|
const { push, currentRoute } = useRouter() // 路由
|
||||||
const { params } = useRoute() // 查询参数
|
const { params, name } = useRoute() // 查询参数
|
||||||
const { delView } = useTagsViewStore() // 视图操作
|
const { delView } = useTagsViewStore() // 视图操作
|
||||||
|
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
const activeName = ref('basicInfo') // Tag 激活的窗口
|
const activeName = ref('basicInfo') // Tag 激活的窗口
|
||||||
|
const isDetail = ref(false) // 是否查看详情
|
||||||
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() // 商品信息Ref
|
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() // 商品信息Ref
|
||||||
const descriptionRef = ref<ComponentRef<typeof DescriptionForm>>() // 商品详情Ref
|
const descriptionRef = ref<ComponentRef<typeof DescriptionForm>>() // 商品详情Ref
|
||||||
const otherSettingsRef = ref<ComponentRef<typeof OtherSettingsForm>>() // 其他设置Ref
|
const otherSettingsRef = ref<ComponentRef<typeof OtherSettingsForm>>() // 其他设置Ref
|
||||||
// spu 表单数据
|
// spu 表单数据
|
||||||
const formData = ref<ProductSpuApi.SpuType>({
|
const formData = ref<ProductSpuApi.Spu>({
|
||||||
name: '', // 商品名称
|
name: '', // 商品名称
|
||||||
categoryId: null, // 商品分类
|
categoryId: null, // 商品分类
|
||||||
keyword: '', // 关键字
|
keyword: '', // 关键字
|
||||||
@ -59,7 +65,7 @@ const formData = ref<ProductSpuApi.SpuType>({
|
|||||||
picUrl: '', // 商品封面图
|
picUrl: '', // 商品封面图
|
||||||
sliderPicUrls: [], // 商品轮播图
|
sliderPicUrls: [], // 商品轮播图
|
||||||
introduction: '', // 商品简介
|
introduction: '', // 商品简介
|
||||||
deliveryTemplateId: 1, // 运费模版
|
deliveryTemplateId: null, // 运费模版
|
||||||
brandId: null, // 商品品牌
|
brandId: null, // 商品品牌
|
||||||
specType: false, // 商品规格
|
specType: false, // 商品规格
|
||||||
subCommissionType: false, // 分销类型
|
subCommissionType: false, // 分销类型
|
||||||
@ -90,12 +96,15 @@ const formData = ref<ProductSpuApi.SpuType>({
|
|||||||
|
|
||||||
/** 获得详情 */
|
/** 获得详情 */
|
||||||
const getDetail = async () => {
|
const getDetail = async () => {
|
||||||
|
if ('productSpuDetail' === name) {
|
||||||
|
isDetail.value = true
|
||||||
|
}
|
||||||
const id = params.spuId as number
|
const id = params.spuId as number
|
||||||
if (id) {
|
if (id) {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.SpuType
|
const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.Spu
|
||||||
res.skus.forEach((item) => {
|
res.skus!.forEach((item) => {
|
||||||
// 回显价格分转元
|
// 回显价格分转元
|
||||||
item.price = formatToFraction(item.price)
|
item.price = formatToFraction(item.price)
|
||||||
item.marketPrice = formatToFraction(item.marketPrice)
|
item.marketPrice = formatToFraction(item.marketPrice)
|
||||||
@ -120,9 +129,10 @@ const submitForm = async () => {
|
|||||||
await unref(basicInfoRef)?.validate()
|
await unref(basicInfoRef)?.validate()
|
||||||
await unref(descriptionRef)?.validate()
|
await unref(descriptionRef)?.validate()
|
||||||
await unref(otherSettingsRef)?.validate()
|
await unref(otherSettingsRef)?.validate()
|
||||||
const deepCopyFormData = cloneDeep(unref(formData.value)) // 深拷贝一份 fix:这样最终 server 端不满足,不需要恢复,
|
// 深拷贝一份, 这样最终 server 端不满足,不需要恢复,
|
||||||
// TODO 兜底处理 sku 空数据
|
const deepCopyFormData = cloneDeep(unref(formData.value))
|
||||||
formData.value.skus.forEach((sku) => {
|
// 兜底处理 sku 空数据
|
||||||
|
formData.value.skus!.forEach((sku) => {
|
||||||
// 因为是空数据这里判断一下商品条码是否为空就行
|
// 因为是空数据这里判断一下商品条码是否为空就行
|
||||||
if (sku.barCode === '') {
|
if (sku.barCode === '') {
|
||||||
const index = deepCopyFormData.skus.findIndex(
|
const index = deepCopyFormData.skus.findIndex(
|
||||||
@ -150,7 +160,7 @@ const submitForm = async () => {
|
|||||||
})
|
})
|
||||||
deepCopyFormData.sliderPicUrls = newSliderPicUrls
|
deepCopyFormData.sliderPicUrls = newSliderPicUrls
|
||||||
// 校验都通过后提交表单
|
// 校验都通过后提交表单
|
||||||
const data = deepCopyFormData as ProductSpuApi.SpuType
|
const data = deepCopyFormData as ProductSpuApi.Spu
|
||||||
const id = params.spuId as number
|
const id = params.spuId as number
|
||||||
if (!id) {
|
if (!id) {
|
||||||
await ProductSpuApi.createSpu(data)
|
await ProductSpuApi.createSpu(data)
|
||||||
@ -170,7 +180,6 @@ const close = () => {
|
|||||||
delView(unref(currentRoute))
|
delView(unref(currentRoute))
|
||||||
push('/product/product-spu')
|
push('/product/product-spu')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getDetail()
|
await getDetail()
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
<template>
|
<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-row>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="商品名称" prop="name">
|
<el-form-item label="商品名称" prop="name">
|
||||||
@ -7,7 +14,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<!-- TODO @puhui999:只能选根节点 -->
|
<!-- TODO @puhui999:只能选根节点 fix: 已完善-->
|
||||||
<el-form-item label="商品分类" prop="categoryId">
|
<el-form-item label="商品分类" prop="categoryId">
|
||||||
<el-tree-select
|
<el-tree-select
|
||||||
v-model="formData.categoryId"
|
v-model="formData.categoryId"
|
||||||
@ -17,6 +24,7 @@
|
|||||||
class="w-1/1"
|
class="w-1/1"
|
||||||
node-key="id"
|
node-key="id"
|
||||||
placeholder="请选择商品分类"
|
placeholder="请选择商品分类"
|
||||||
|
@change="categoryNodeClick"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -60,9 +68,15 @@
|
|||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="运费模板" prop="deliveryTemplateId">
|
<el-form-item label="运费模板" prop="deliveryTemplateId">
|
||||||
<el-select v-model="formData.deliveryTemplateId" placeholder="请选择">
|
<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-select>
|
||||||
<el-button class="ml-20px">运费模板</el-button>
|
<!-- TODO 可能情况:善品录入后选择运费发现下拉选择中没有对应的模版 这里需不需要做添加运费模版后选择的功能 -->
|
||||||
|
<!-- <el-button class="ml-20px">运费模板</el-button>-->
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@ -95,6 +109,9 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<!-- 多规格添加-->
|
<!-- 多规格添加-->
|
||||||
<el-col :span="24">
|
<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-form-item v-if="formData.specType" label="商品属性">
|
||||||
<el-button class="mr-15px mb-10px" @click="attributesAddFormRef.open">添加规格</el-button>
|
<el-button class="mr-15px mb-10px" @click="attributesAddFormRef.open">添加规格</el-button>
|
||||||
<ProductAttributes :propertyList="propertyList" @success="generateSkus" />
|
<ProductAttributes :propertyList="propertyList" @success="generateSkus" />
|
||||||
@ -107,34 +124,93 @@
|
|||||||
<SkuList ref="skuListRef" :prop-form-data="formData" :propertyList="propertyList" />
|
<SkuList ref="skuListRef" :prop-form-data="formData" :propertyList="propertyList" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
<el-form-item v-if="!formData.specType">
|
|
||||||
<SkuList :prop-form-data="formData" :propertyList="propertyList" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 情况二:详情 -->
|
||||||
|
<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 表单 -->
|
||||||
|
<!-- TODO @puhui999: ProductPropertyAddForm 是不是更合适呀 -->
|
||||||
<ProductAttributesAddForm ref="attributesAddFormRef" :propertyList="propertyList" />
|
<ProductAttributesAddForm ref="attributesAddFormRef" :propertyList="propertyList" />
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" name="ProductSpuBasicInfoForm" setup>
|
<script lang="ts" name="ProductSpuBasicInfoForm" setup>
|
||||||
import { PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
|
import { isArray } from '@/utils/is'
|
||||||
import { copyValueToTarget } from '@/utils'
|
import { copyValueToTarget } from '@/utils'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
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 { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import type { SpuType } from '@/api/mall/product/spu'
|
|
||||||
import { UploadImg, UploadImgs } from '@/components/UploadFile'
|
import { UploadImg, UploadImgs } from '@/components/UploadFile'
|
||||||
import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index'
|
import { ProductAttributes, ProductAttributesAddForm, 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 * as ProductCategoryApi from '@/api/mall/product/category'
|
||||||
import { getSimpleBrandList } from '@/api/mall/product/brand'
|
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 ======
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
propFormData: {
|
propFormData: {
|
||||||
type: Object as PropType<SpuType>,
|
type: Object as PropType<Spu>,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
},
|
},
|
||||||
activeName: propTypes.string.def('')
|
activeName: propTypes.string.def(''),
|
||||||
|
isDetail: propTypes.bool.def(false) // 是否作为详情组件
|
||||||
})
|
})
|
||||||
const attributesAddFormRef = ref() // 添加商品属性表单
|
const attributesAddFormRef = ref() // 添加商品属性表单
|
||||||
const productSpuBasicInfoRef = ref() // 表单 Ref
|
const productSpuBasicInfoRef = ref() // 表单 Ref
|
||||||
@ -144,15 +220,15 @@ const skuListRef = ref() // 商品属性列表Ref
|
|||||||
const generateSkus = (propertyList) => {
|
const generateSkus = (propertyList) => {
|
||||||
skuListRef.value.generateTableData(propertyList)
|
skuListRef.value.generateTableData(propertyList)
|
||||||
}
|
}
|
||||||
const formData = reactive<SpuType>({
|
const formData = reactive<Spu>({
|
||||||
name: '', // 商品名称
|
name: '', // 商品名称
|
||||||
categoryId: null, // 商品分类
|
categoryId: null, // 商品分类
|
||||||
keyword: '', // 关键字
|
keyword: '', // 关键字
|
||||||
unit: '', // 单位
|
unit: null, // 单位
|
||||||
picUrl: '', // 商品封面图
|
picUrl: '', // 商品封面图
|
||||||
sliderPicUrls: [], // 商品轮播图
|
sliderPicUrls: [], // 商品轮播图
|
||||||
introduction: '', // 商品简介
|
introduction: '', // 商品简介
|
||||||
deliveryTemplateId: 1, // 运费模版
|
deliveryTemplateId: null, // 运费模版
|
||||||
brandId: null, // 商品品牌
|
brandId: null, // 商品品牌
|
||||||
specType: false, // 商品规格
|
specType: false, // 商品规格
|
||||||
subCommissionType: false, // 分销类型
|
subCommissionType: false, // 分销类型
|
||||||
@ -185,9 +261,10 @@ watch(
|
|||||||
formData.sliderPicUrls = data['sliderPicUrls'].map((item) => ({
|
formData.sliderPicUrls = data['sliderPicUrls'].map((item) => ({
|
||||||
url: item
|
url: item
|
||||||
}))
|
}))
|
||||||
// TODO @puhui999:if return,减少嵌套层级
|
|
||||||
// 只有是多规格才处理
|
// 只有是多规格才处理
|
||||||
if (formData.specType) {
|
if (!formData.specType) {
|
||||||
|
return
|
||||||
|
}
|
||||||
// 直接拿返回的 skus 属性逆向生成出 propertyList
|
// 直接拿返回的 skus 属性逆向生成出 propertyList
|
||||||
const properties = []
|
const properties = []
|
||||||
formData.skus.forEach((sku) => {
|
formData.skus.forEach((sku) => {
|
||||||
@ -204,7 +281,6 @@ watch(
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
propertyList.value = properties
|
propertyList.value = properties
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true
|
immediate: true
|
||||||
@ -216,6 +292,11 @@ watch(
|
|||||||
*/
|
*/
|
||||||
const emit = defineEmits(['update:activeName'])
|
const emit = defineEmits(['update:activeName'])
|
||||||
const validate = async () => {
|
const validate = async () => {
|
||||||
|
// 校验 sku
|
||||||
|
if (!skuListRef.value.validateSku()) {
|
||||||
|
message.warning('商品相关价格不能低于 0.01 元!!')
|
||||||
|
throw new Error('商品相关价格不能低于 0.01 元!!')
|
||||||
|
}
|
||||||
// 校验表单
|
// 校验表单
|
||||||
if (!productSpuBasicInfoRef) return
|
if (!productSpuBasicInfoRef) return
|
||||||
return await unref(productSpuBasicInfoRef).validate((valid) => {
|
return await unref(productSpuBasicInfoRef).validate((valid) => {
|
||||||
@ -263,12 +344,32 @@ const onChangeSpec = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const categoryList = ref([]) // 分类树
|
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 brandList = ref([]) // 精简商品品牌列表
|
||||||
|
const deliveryTemplateList = ref([]) // 运费模版
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 获得分类树
|
// 获得分类树
|
||||||
const data = await ProductCategoryApi.getCategoryList({})
|
const data = await ProductCategoryApi.getCategoryList({})
|
||||||
categoryList.value = handleTree(data, 'id', 'parentId')
|
categoryList.value = handleTree(data, 'id', 'parentId')
|
||||||
// 获取商品品牌列表
|
// 获取商品品牌列表
|
||||||
brandList.value = await getSimpleBrandList()
|
brandList.value = await getSimpleBrandList()
|
||||||
|
// 获取运费模版
|
||||||
|
deliveryTemplateList.value = await getSimpleTemplateList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,28 +1,51 @@
|
|||||||
<template>
|
<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">
|
<el-form-item label="商品详情" prop="description">
|
||||||
<Editor v-model:modelValue="formData.description" />
|
<Editor v-model:modelValue="formData.description" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</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>
|
</template>
|
||||||
<script lang="ts" name="DescriptionForm" setup>
|
<script lang="ts" name="DescriptionForm" setup>
|
||||||
import type { SpuType } from '@/api/mall/product/spu'
|
import type { Spu } from '@/api/mall/product/spu'
|
||||||
import { Editor } from '@/components/Editor'
|
import { Editor } from '@/components/Editor'
|
||||||
import { PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { copyValueToTarget } from '@/utils'
|
import { copyValueToTarget } from '@/utils'
|
||||||
|
import { descriptionSchema } from './spu.data'
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
const { allSchemas } = useCrudSchemas(descriptionSchema)
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
propFormData: {
|
propFormData: {
|
||||||
type: Object as PropType<SpuType>,
|
type: Object as PropType<Spu>,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
},
|
},
|
||||||
activeName: propTypes.string.def('')
|
activeName: propTypes.string.def(''),
|
||||||
|
isDetail: propTypes.bool.def(false) // 是否作为详情组件
|
||||||
})
|
})
|
||||||
const descriptionFormRef = ref() // 表单Ref
|
const descriptionFormRef = ref() // 表单Ref
|
||||||
const formData = ref<SpuType>({
|
const formData = ref<Spu>({
|
||||||
description: '' // 商品详情
|
description: '' // 商品详情
|
||||||
})
|
})
|
||||||
// 表单规则
|
// 表单规则
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
<template>
|
<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-row>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
@ -50,26 +57,55 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</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>
|
</template>
|
||||||
<script lang="ts" name="OtherSettingsForm" setup>
|
<script lang="ts" name="OtherSettingsForm" setup>
|
||||||
import type { SpuType } from '@/api/mall/product/spu'
|
import type { Spu } from '@/api/mall/product/spu'
|
||||||
import { PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { copyValueToTarget } from '@/utils'
|
import { copyValueToTarget } from '@/utils'
|
||||||
|
import { otherSettingsSchema } from './spu.data'
|
||||||
|
|
||||||
|
const { allSchemas } = useCrudSchemas(otherSettingsSchema)
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
propFormData: {
|
propFormData: {
|
||||||
type: Object as PropType<SpuType>,
|
type: Object as PropType<Spu>,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
},
|
},
|
||||||
activeName: propTypes.string.def('')
|
activeName: propTypes.string.def(''),
|
||||||
|
isDetail: propTypes.bool.def(false) // 是否作为详情组件
|
||||||
})
|
})
|
||||||
|
|
||||||
const otherSettingsFormRef = ref() // 表单Ref
|
const otherSettingsFormRef = ref() // 表单Ref
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const formData = ref<SpuType>({
|
const formData = ref<Spu>({
|
||||||
sort: 1, // 商品排序
|
sort: 1, // 商品排序
|
||||||
giveIntegral: 1, // 赠送积分
|
giveIntegral: 1, // 赠送积分
|
||||||
virtualSalesCount: 1, // 虚拟销量
|
virtualSalesCount: 1, // 虚拟销量
|
||||||
|
@ -90,8 +90,7 @@ const submitForm = async () => {
|
|||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
name: '',
|
name: ''
|
||||||
remark: ''
|
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<!-- 情况一:添加/修改 -->
|
||||||
<el-table
|
<el-table
|
||||||
:data="isBatch ? skuList : formData.skus"
|
v-if="!isDetail"
|
||||||
|
:data="isBatch ? skuList : formData!.skus"
|
||||||
border
|
border
|
||||||
class="tabNumWidth"
|
class="tabNumWidth"
|
||||||
max-height="500"
|
max-height="500"
|
||||||
@ -11,7 +13,7 @@
|
|||||||
<UploadImg v-model="row.picUrl" height="80px" width="100%" />
|
<UploadImg v-model="row.picUrl" height="80px" width="100%" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<template v-if="formData.specType && !isBatch">
|
<template v-if="formData!.specType && !isBatch">
|
||||||
<!-- 根据商品属性动态添加 -->
|
<!-- 根据商品属性动态添加 -->
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="(item, index) in tableHeaders"
|
v-for="(item, index) in tableHeaders"
|
||||||
@ -21,8 +23,10 @@
|
|||||||
min-width="120"
|
min-width="120"
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<!-- TODO puhui999:展示成蓝色,有点区分度哈 -->
|
<!-- TODO puhui999:展示成蓝色,有点区分度哈 fix-->
|
||||||
|
<span style="font-weight: bold; color: #40aaff">
|
||||||
{{ row.properties[index]?.valueName }}
|
{{ row.properties[index]?.valueName }}
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</template>
|
</template>
|
||||||
@ -73,7 +77,7 @@
|
|||||||
<el-input-number v-model="row.volume" :min="0" :precision="2" :step="0.1" class="w-100%" />
|
<el-input-number v-model="row.volume" :min="0" :precision="2" :step="0.1" class="w-100%" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<template v-if="formData.subCommissionType">
|
<template v-if="formData!.subCommissionType">
|
||||||
<el-table-column align="center" label="一级返佣(元)" min-width="168">
|
<el-table-column align="center" label="一级返佣(元)" min-width="168">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
@ -97,7 +101,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</template>
|
</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 }">
|
<template #default="{ row }">
|
||||||
<el-button v-if="isBatch" link size="small" type="primary" @click="batchAdd">
|
<el-button v-if="isBatch" link size="small" type="primary" @click="batchAdd">
|
||||||
批量添加
|
批量添加
|
||||||
@ -106,27 +110,108 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 情况二:详情 -->
|
||||||
|
<el-table
|
||||||
|
v-if="isDetail"
|
||||||
|
:data="formData!.skus"
|
||||||
|
border
|
||||||
|
max-height="500"
|
||||||
|
size="small"
|
||||||
|
style="width: 99%"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" name="SkuList" setup>
|
<script lang="ts" name="SkuList" setup>
|
||||||
import { PropType } from 'vue'
|
import { PropType, Ref } from 'vue'
|
||||||
import { copyValueToTarget } from '@/utils'
|
import { copyValueToTarget } from '@/utils'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { UploadImg } from '@/components/UploadFile'
|
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'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
propFormData: {
|
propFormData: {
|
||||||
type: Object as PropType<SpuType>,
|
type: Object as PropType<Spu>,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
},
|
},
|
||||||
propertyList: {
|
propertyList: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
isBatch: propTypes.bool.def(false) // 是否作为批量操作组件
|
isBatch: propTypes.bool.def(false), // 是否作为批量操作组件
|
||||||
|
isDetail: propTypes.bool.def(false) // 是否作为 sku 详情组件
|
||||||
})
|
})
|
||||||
const formData = ref<SpuType>() // 表单数据
|
const formData: Ref<Spu | undefined> = ref<Spu>() // 表单数据
|
||||||
const skuList = ref<SkuType[]>([
|
const skuList = ref<Sku[]>([
|
||||||
{
|
{
|
||||||
price: 0, // 商品价格
|
price: 0, // 商品价格
|
||||||
marketPrice: 0, // 市场价
|
marketPrice: 0, // 市场价
|
||||||
@ -140,24 +225,44 @@ const skuList = ref<SkuType[]>([
|
|||||||
subCommissionSecondPrice: 0 // 二级分销的佣金
|
subCommissionSecondPrice: 0 // 二级分销的佣金
|
||||||
}
|
}
|
||||||
]) // 批量添加时的临时数据
|
]) // 批量添加时的临时数据
|
||||||
// TODO @puhui999:保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
|
|
||||||
|
/** 商品图预览 */
|
||||||
|
const imagePreview = (imgUrl: string) => {
|
||||||
|
createImageViewer({
|
||||||
|
urlList: [imgUrl]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/** 批量添加 */
|
/** 批量添加 */
|
||||||
const batchAdd = () => {
|
const batchAdd = () => {
|
||||||
formData.value.skus.forEach((item) => {
|
formData.value!.skus!.forEach((item) => {
|
||||||
copyValueToTarget(item, skuList.value[0])
|
copyValueToTarget(item, skuList.value[0])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除 sku */
|
/** 删除 sku */
|
||||||
const deleteSku = (row) => {
|
const deleteSku = (row) => {
|
||||||
const index = formData.value.skus.findIndex(
|
const index = formData.value!.skus!.findIndex(
|
||||||
// 直接把列表转成字符串比较
|
// 直接把列表转成字符串比较
|
||||||
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
|
(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 }[]>([]) // 多属性表头
|
const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表头
|
||||||
|
/**
|
||||||
|
* 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
|
||||||
|
*/
|
||||||
|
const validateSku = (): boolean => {
|
||||||
|
const checks = ['price', 'marketPrice', 'costPrice']
|
||||||
|
let validate = true // 默认通过
|
||||||
|
for (const sku of formData.value!.skus) {
|
||||||
|
if (checks.some((check) => sku[check] < 0.01)) {
|
||||||
|
validate = false // 只要有一个不通过则直接不通过
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validate
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将传进来的值赋值给 skuList
|
* 将传进来的值赋值给 skuList
|
||||||
@ -185,14 +290,13 @@ const generateTableData = (propertyList: any[]) => {
|
|||||||
valueName: v.name
|
valueName: v.name
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
// TODO @puhui:是不是 buildSkuList,这样容易理解一点哈。item 改成 sku
|
const buildSkuList = build(propertyValues)
|
||||||
const buildList = build(propertyValues)
|
|
||||||
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
|
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
|
||||||
if (!validateData(propertyList)) {
|
if (!validateData(propertyList)) {
|
||||||
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
|
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
|
||||||
formData.value!.skus = []
|
formData.value!.skus = []
|
||||||
}
|
}
|
||||||
for (const item of buildList) {
|
for (const item of buildSkuList) {
|
||||||
const row = {
|
const row = {
|
||||||
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象
|
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象
|
||||||
price: 0,
|
price: 0,
|
||||||
@ -207,13 +311,13 @@ const generateTableData = (propertyList: any[]) => {
|
|||||||
subCommissionSecondPrice: 0
|
subCommissionSecondPrice: 0
|
||||||
}
|
}
|
||||||
// 如果存在属性相同的 sku 则不做处理
|
// 如果存在属性相同的 sku 则不做处理
|
||||||
const index = formData.value!.skus.findIndex(
|
const index = formData.value!.skus!.findIndex(
|
||||||
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
|
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
|
||||||
)
|
)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
formData.value.skus.push(row)
|
formData.value!.skus!.push(row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,7 +326,7 @@ const generateTableData = (propertyList: any[]) => {
|
|||||||
*/
|
*/
|
||||||
const validateData = (propertyList: any[]) => {
|
const validateData = (propertyList: any[]) => {
|
||||||
const skuPropertyIds = []
|
const skuPropertyIds = []
|
||||||
formData.value.skus.forEach((sku) =>
|
formData.value!.skus!.forEach((sku) =>
|
||||||
sku.properties
|
sku.properties
|
||||||
?.map((property) => property.propertyId)
|
?.map((property) => property.propertyId)
|
||||||
.forEach((propertyId) => {
|
.forEach((propertyId) => {
|
||||||
@ -263,7 +367,7 @@ watch(
|
|||||||
() => props.propertyList,
|
() => props.propertyList,
|
||||||
(propertyList) => {
|
(propertyList) => {
|
||||||
// 如果不是多规格则结束
|
// 如果不是多规格则结束
|
||||||
if (!formData.value.specType) {
|
if (!formData.value!.specType) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 如果当前组件作为批量添加数据使用,则重置表数据
|
// 如果当前组件作为批量添加数据使用,则重置表数据
|
||||||
@ -313,5 +417,5 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
// 暴露出生成 sku 方法,给添加属性成功时调用
|
// 暴露出生成 sku 方法,给添加属性成功时调用
|
||||||
defineExpose({ generateTableData })
|
defineExpose({ generateTableData, validateSku })
|
||||||
</script>
|
</script>
|
||||||
|
106
src/views/mall/product/spu/components/spu.data.ts
Normal file
106
src/views/mall/product/spu/components/spu.data.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { CrudSchema } from '@/hooks/web/useCrudSchemas'
|
||||||
|
|
||||||
|
// TODO @puhui999:如果只要 detail,可以不用 CrudSchema,只要描述的 Schema
|
||||||
|
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,16 @@
|
|||||||
class="-mb-15px"
|
class="-mb-15px"
|
||||||
label-width="68px"
|
label-width="68px"
|
||||||
>
|
>
|
||||||
<!-- TODO @puhui999:品牌应该是数据下拉哈 -->
|
<el-form-item label="商品名称" prop="name">
|
||||||
<el-form-item label="品牌名称" prop="name">
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.name"
|
v-model="queryParams.name"
|
||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请输入品牌名称"
|
placeholder="请输入商品名称"
|
||||||
@keyup.enter="handleQuery"
|
@keyup.enter="handleQuery"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- TODO 分类只能选择二级分类目前还没做,还是先以联调通顺为主 -->
|
<!-- TODO 分类只能选择二级分类目前还没做,还是先以联调通顺为主 fixL: 已完善 -->
|
||||||
<!-- TODO puhui999:我们要不改成支持选择一级。如果选择一级,后端要递归查询下子分类,然后去 in? -->
|
|
||||||
<el-form-item label="商品分类" prop="categoryId">
|
<el-form-item label="商品分类" prop="categoryId">
|
||||||
<el-tree-select
|
<el-tree-select
|
||||||
v-model="queryParams.categoryId"
|
v-model="queryParams.categoryId"
|
||||||
@ -29,6 +27,7 @@
|
|||||||
class="w-1/1"
|
class="w-1/1"
|
||||||
node-key="id"
|
node-key="id"
|
||||||
placeholder="请选择商品分类"
|
placeholder="请选择商品分类"
|
||||||
|
@change="nodeClick"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="创建时间" prop="createTime">
|
<el-form-item label="创建时间" prop="createTime">
|
||||||
@ -80,7 +79,7 @@
|
|||||||
/>
|
/>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<el-table v-loading="loading" :data="list">
|
<el-table v-loading="loading" :data="list">
|
||||||
<!-- TODO puhui:这几个属性哈,一行三个
|
<!-- TODO puhui:这几个属性哈,一行三个 fix
|
||||||
商品分类:服装鞋包/箱包
|
商品分类:服装鞋包/箱包
|
||||||
商品市场价格:100.00
|
商品市场价格:100.00
|
||||||
成本价:0.00
|
成本价:0.00
|
||||||
@ -88,23 +87,52 @@
|
|||||||
虚拟销量:999 -->
|
虚拟销量:999 -->
|
||||||
<el-table-column type="expand" width="30">
|
<el-table-column type="expand" width="30">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-form class="demo-table-expand" inline label-position="left">
|
<el-form class="demo-table-expand" label-position="left">
|
||||||
<el-form-item label="市场价:">
|
<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>
|
<span>{{ formatToFraction(row.marketPrice) }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="成本价:">
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="成本价:">
|
||||||
<span>{{ formatToFraction(row.costPrice) }}</span>
|
<span>{{ formatToFraction(row.costPrice) }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="虚拟销量:">
|
</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>
|
<span>{{ row.virtualSalesCount }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column key="id" align="center" label="商品编号" prop="id" />
|
<el-table-column key="id" align="center" label="商品编号" prop="id" />
|
||||||
<el-table-column label="商品图" min-width="80">
|
<el-table-column label="商品图" min-width="80">
|
||||||
<template #default="{ row }">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
|
<el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
|
||||||
@ -143,8 +171,12 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column align="center" fixed="right" label="操作" min-width="200">
|
<el-table-column align="center" fixed="right" label="操作" min-width="200">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<!-- TODO @puhui999:【详情】,可以后面点做哈 -->
|
<el-button
|
||||||
<el-button v-hasPermi="['product:spu:update']" link type="primary" @click="openDetail">
|
v-hasPermi="['product:spu:update']"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openDetail(row.id)"
|
||||||
|
>
|
||||||
详情
|
详情
|
||||||
</el-button>
|
</el-button>
|
||||||
<template v-if="queryParams.tabType === 4">
|
<template v-if="queryParams.tabType === 4">
|
||||||
@ -202,7 +234,7 @@ import { TabsPaneContext } from 'element-plus'
|
|||||||
import { cloneDeep } from 'lodash-es'
|
import { cloneDeep } from 'lodash-es'
|
||||||
import { createImageViewer } from '@/components/ImageViewer'
|
import { createImageViewer } from '@/components/ImageViewer'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
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 { ProductSpuStatusEnum } from '@/utils/constants'
|
||||||
import { formatToFraction } from '@/utils'
|
import { formatToFraction } from '@/utils'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
@ -256,12 +288,14 @@ const getTabsCount = async () => {
|
|||||||
const queryParams = ref({
|
const queryParams = ref({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
tabType: 0
|
tabType: 0,
|
||||||
|
name: '',
|
||||||
|
categoryId: null
|
||||||
}) // 查询参数
|
}) // 查询参数
|
||||||
const queryFormRef = ref() // 搜索的表单Ref
|
const queryFormRef = ref() // 搜索的表单Ref
|
||||||
|
|
||||||
const handleTabClick = (tab: TabsPaneContext) => {
|
const handleTabClick = (tab: TabsPaneContext) => {
|
||||||
queryParams.value.tabType = tab.paneName
|
queryParams.value.tabType = tab.paneName as number
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,8 +406,8 @@ const openForm = (id?: number) => {
|
|||||||
/**
|
/**
|
||||||
* 查看商品详情
|
* 查看商品详情
|
||||||
*/
|
*/
|
||||||
const openDetail = () => {
|
const openDetail = (id?: number) => {
|
||||||
message.alert('查看详情未完善!!!')
|
push('/product/productSpuDetail/' + id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 导出按钮操作 */
|
/** 导出按钮操作 */
|
||||||
@ -391,7 +425,7 @@ const handleExport = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新
|
// 监听路由变化更新列表,解决商品保存后,列表不刷新的问题。
|
||||||
watch(
|
watch(
|
||||||
() => currentRoute.value,
|
() => currentRoute.value,
|
||||||
() => {
|
() => {
|
||||||
@ -400,6 +434,22 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const categoryList = ref() // 分类树
|
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 () => {
|
onMounted(async () => {
|
||||||
await getTabsCount()
|
await getTabsCount()
|
||||||
|
200
src/views/mall/promotion/coupon/index.vue
Executable file
200
src/views/mall/promotion/coupon/index.vue
Executable file
@ -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>
|
614
src/views/mall/promotion/couponTemplate/index.vue
Executable file
614
src/views/mall/promotion/couponTemplate/index.vue
Executable file
@ -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>
|
@ -172,6 +172,9 @@ const formRules = reactive({
|
|||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
const areaCache = ref([]) // 由于区域节点懒加载,已选区域节点需要缓存展示
|
const areaCache = ref([]) // 由于区域节点懒加载,已选区域节点需要缓存展示
|
||||||
|
// TODO @jason:配送的时候,只允许选择省市级别,不允许选择区;如果这样的话,是不是打开弹窗,直接把城市都请求过来;
|
||||||
|
// TODO @jaosn:因为只有省市两级,感觉就不用特殊做全国逻辑;选择全国,就默认把子节点都选择上;另外,选择父节点,要把子节点选中哈;
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (type: string, id?: number) => {
|
const open = async (type: string, id?: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
@ -226,8 +229,9 @@ const submitForm = async () => {
|
|||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
const data = formData.value as DeliveryExpressTemplateApi.DeliveryExpressTemplateVO
|
const data = formData.value as DeliveryExpressTemplateApi.DeliveryExpressTemplateVO
|
||||||
data.templateCharge.forEach((item) => {
|
|
||||||
// 前端价格以元展示,提交到后端。用分计算
|
// 前端价格以元展示,提交到后端。用分计算
|
||||||
|
// TODO @jason:不能直接这样改,要复制出来改。不然后端操作失败,数据已经被改了
|
||||||
|
data.templateCharge.forEach((item) => {
|
||||||
item.startPrice = yuanToFen(item.startPrice)
|
item.startPrice = yuanToFen(item.startPrice)
|
||||||
item.extraPrice = yuanToFen(item.extraPrice)
|
item.extraPrice = yuanToFen(item.extraPrice)
|
||||||
})
|
})
|
||||||
@ -248,6 +252,7 @@ const submitForm = async () => {
|
|||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
@ -269,6 +274,7 @@ const resetForm = () => {
|
|||||||
columnTitle.value = columnTitleMap.get(1)
|
columnTitle.value = columnTitleMap.get(1)
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 配送计费方法改变 */
|
/** 配送计费方法改变 */
|
||||||
const changeChargeMode = (chargeMode: number) => {
|
const changeChargeMode = (chargeMode: number) => {
|
||||||
columnTitle.value = columnTitleMap.get(chargeMode)
|
columnTitle.value = columnTitleMap.get(chargeMode)
|
||||||
@ -276,6 +282,24 @@ const changeChargeMode = (chargeMode: number) => {
|
|||||||
const defaultArea = [{ id: 1, name: '全国', disabled: false }]
|
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 () => {
|
const initData = async () => {
|
||||||
// TODO 从服务端全量加载数据, 后面看懒加载是不是可以从前端获取数据。 目前从后端获取数据
|
// TODO 从服务端全量加载数据, 后面看懒加载是不是可以从前端获取数据。 目前从后端获取数据
|
||||||
// formLoading.value = true
|
// formLoading.value = true
|
||||||
@ -320,6 +344,7 @@ const loadChargeArea = async (node, resolve) => {
|
|||||||
const item = data[0]
|
const item = data[0]
|
||||||
if (areaIds.includes(item.id)) {
|
if (areaIds.includes(item.id)) {
|
||||||
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
|
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
|
||||||
|
// TODO @jason:先不做这个功能哈。
|
||||||
//item.disabled = true
|
//item.disabled = true
|
||||||
}
|
}
|
||||||
resolve(data)
|
resolve(data)
|
||||||
@ -361,6 +386,7 @@ const loadFreeArea = async (node, resolve) => {
|
|||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
if (areaIds.includes(item.id)) {
|
if (areaIds.includes(item.id)) {
|
||||||
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
|
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
|
||||||
|
// TODO @jason:先不做这个功能哈。
|
||||||
//item.disabled = true
|
//item.disabled = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -378,11 +404,13 @@ const addChargeArea = () => {
|
|||||||
extraPrice: 1
|
extraPrice: 1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除计费区域 */
|
/** 删除计费区域 */
|
||||||
const deleteChargeArea = (index) => {
|
const deleteChargeArea = (index) => {
|
||||||
const data = formData.value
|
const data = formData.value
|
||||||
data.templateCharge.splice(index, 1)
|
data.templateCharge.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加包邮区域 */
|
/** 添加包邮区域 */
|
||||||
const addFreeArea = () => {
|
const addFreeArea = () => {
|
||||||
const data = formData.value
|
const data = formData.value
|
||||||
@ -392,6 +420,7 @@ const addFreeArea = () => {
|
|||||||
freePrice: 1
|
freePrice: 1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除包邮区域 */
|
/** 删除包邮区域 */
|
||||||
const deleteFreeArea = (index) => {
|
const deleteFreeArea = (index) => {
|
||||||
const data = formData.value
|
const data = formData.value
|
||||||
|
@ -110,6 +110,7 @@ const queryParams = reactive({
|
|||||||
chargeMode: undefined
|
chargeMode: undefined
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
287
src/views/mall/trade/delivery/pickUpStore/PickUpStoreForm.vue
Normal file
287
src/views/mall/trade/delivery/pickUpStore/PickUpStoreForm.vue
Normal file
@ -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>
|
201
src/views/mall/trade/delivery/pickUpStore/index.vue
Normal file
201
src/views/mall/trade/delivery/pickUpStore/index.vue
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<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 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"
|
||||||
|
plain
|
||||||
|
@click="openForm('create')"
|
||||||
|
v-hasPermi="['trade:delivery:pick-up-store:create']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
v-hasPermi="['trade:delivery:pick-up-store:export']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list">
|
||||||
|
<el-table-column label="编号" prop="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"
|
||||||
|
width="180"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
/>
|
||||||
|
<el-table-column label="操作" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('update', scope.row.id)"
|
||||||
|
v-hasPermi="['trade:delivery:pick-up-store:update']"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(scope.row.id)"
|
||||||
|
v-hasPermi="['trade:delivery:pick-up-store:delete']"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</ContentWrap>
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<DeliveryPickUpStoreForm ref="formRef" @success="getList" />
|
||||||
|
</template>
|
||||||
|
<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'
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
|
const total = ref(0) // 列表的总页数
|
||||||
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
const list = ref<any[]>([]) // 列表的数据
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
status: undefined,
|
||||||
|
phone: undefined,
|
||||||
|
name: undefined,
|
||||||
|
createTime: []
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
|
/** 添加/修改操作 */
|
||||||
|
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 DeliveryPickUpStoreApi.getDeliveryPickUpStorePage(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 handleExport = async () => {
|
||||||
|
try {
|
||||||
|
// 导出的二次确认
|
||||||
|
await message.exportConfirm()
|
||||||
|
// 发起导出
|
||||||
|
exportLoading.value = true
|
||||||
|
const data = await DeliveryPickUpStoreApi.exportDeliveryPickUpStoreApi(queryParams)
|
||||||
|
download.excel(data, '自提门店.xls')
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
@ -3,7 +3,7 @@
|
|||||||
<Descriptions :data="detailData" :schema="allSchemas.detailSchema">
|
<Descriptions :data="detailData" :schema="allSchemas.detailSchema">
|
||||||
<!-- 展示 HTML 内容 -->
|
<!-- 展示 HTML 内容 -->
|
||||||
<template #templateContent="{ row }">
|
<template #templateContent="{ row }">
|
||||||
<div v-html="row.templateContent"></div>
|
<div v-dompurify-html="row.templateContent"></div>
|
||||||
</template>
|
</template>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
Loading…
Reference in New Issue
Block a user