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

This commit is contained in:
puhui999 2023-06-05 08:35:39 +08:00
commit 7fe8dee9ed
19 changed files with 1065 additions and 42 deletions

View File

@ -88,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' })
}

View File

@ -0,0 +1,18 @@
import request from '@/config/axios'
// TODO @dhb52vo 缺少
// 删除优惠劵
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
})
}

View 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'
})
}

View File

@ -52,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 })
}

View File

@ -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: '折扣'
}
}

View File

@ -146,9 +146,12 @@ 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', // 营销的商品范围
// ========== MALL 交易模块 ========== PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE = 'promotion_coupon_template_validity_type', // 优惠劵模板的有限期类型
EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode' //快递的计费方式 PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态
PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式
PROMOTION_ACTIVITY_STATUS = 'promotion_activity_status', // 优惠活动的状态
PROMOTION_CONDITION_TYPE = 'promotion_condition_type' // 营销的条件类型枚举
} }

View File

@ -306,11 +306,14 @@ export const handleTree2 = (data, id, parentId, children, rootId) => {
}) })
return treeData !== '' ? treeData : data return treeData !== '' ? treeData : data
} }
/** /**
* level
* *
* @param tree * @param tree
* @param nodeId * @param nodeId
* @param level , * @param level ,
* @return true false
*/ */
export const checkSelectedNode = (tree: any[], nodeId: any, level = 2): boolean => { export const checkSelectedNode = (tree: any[], nodeId: any, level = 2): boolean => {
if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) { if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) {

View File

@ -1,4 +1,5 @@
<template> <template>
<!-- 情况一添加/修改 -->
<el-form <el-form
v-if="!isDetail" v-if="!isDetail"
ref="productSpuBasicInfoRef" ref="productSpuBasicInfoRef"
@ -23,7 +24,7 @@
class="w-1/1" class="w-1/1"
node-key="id" node-key="id"
placeholder="请选择商品分类" placeholder="请选择商品分类"
@change="nodeClick" @change="categoryNodeClick"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -126,8 +127,8 @@
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
<ProductAttributesAddForm ref="attributesAddFormRef" :propertyList="propertyList" />
<!-- 详情跟表单放在一块可以共用已有功能再抽离成组件有点过度封装的感觉 --> <!-- 情况二详情 -->
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema"> <Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
<template #categoryId="{ row }"> {{ categoryString(row.categoryId) }}</template> <template #categoryId="{ row }"> {{ categoryString(row.categoryId) }}</template>
<template #brandId="{ row }"> <template #brandId="{ row }">
@ -163,6 +164,10 @@
/> />
</template> </template>
</Descriptions> </Descriptions>
<!-- 商品属性添加 Form 表单 -->
<!-- TODO @puhui999: ProductPropertyAddForm 是不是更合适呀 -->
<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'
@ -257,7 +262,9 @@ watch(
url: item url: item
})) }))
// //
if (!formData.specType) return if (!formData.specType) {
return
}
// skus propertyList // skus propertyList
const properties = [] const properties = []
formData.skus.forEach((sku) => { formData.skus.forEach((sku) => {
@ -287,8 +294,8 @@ const emit = defineEmits(['update:activeName'])
const validate = async () => { const validate = async () => {
// sku // sku
if (!skuListRef.value.validateSku()) { if (!skuListRef.value.validateSku()) {
message.warning('商品相关价格不能低于0.01元!!') message.warning('商品相关价格不能低于 0.01 元!!')
throw new Error('商品相关价格不能低于0.01元!!') throw new Error('商品相关价格不能低于 0.01 元!!')
} }
// //
if (!productSpuBasicInfoRef) return if (!productSpuBasicInfoRef) return
@ -340,7 +347,7 @@ const categoryList = ref([]) // 分类树
/** /**
* 选择分类时触发校验 * 选择分类时触发校验
*/ */
const nodeClick = () => { const categoryNodeClick = () => {
if (!checkSelectedNode(categoryList.value, formData.categoryId)) { if (!checkSelectedNode(categoryList.value, formData.categoryId)) {
formData.categoryId = null formData.categoryId = null
message.warning('必须选择二级及以下节点!!') message.warning('必须选择二级及以下节点!!')
@ -348,6 +355,7 @@ const nodeClick = () => {
} }
/** /**
* 获取分类的节点的完整结构 * 获取分类的节点的完整结构
*
* @param categoryId 分类id * @param categoryId 分类id
*/ */
const categoryString = (categoryId) => { const categoryString = (categoryId) => {

View File

@ -1,4 +1,5 @@
<template> <template>
<!-- 情况一添加/修改 -->
<el-form <el-form
v-if="!isDetail" v-if="!isDetail"
ref="descriptionFormRef" ref="descriptionFormRef"
@ -11,6 +12,8 @@
<Editor v-model:modelValue="formData.description" /> <Editor v-model:modelValue="formData.description" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 情况二详情 -->
<Descriptions <Descriptions
v-if="isDetail" v-if="isDetail"
:data="formData" :data="formData"
@ -30,10 +33,9 @@ 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' import { descriptionSchema } from './spu.data'
const message = useMessage() //
const { allSchemas } = useCrudSchemas(descriptionSchema) const { allSchemas } = useCrudSchemas(descriptionSchema)
const message = useMessage() //
const props = defineProps({ const props = defineProps({
propFormData: { propFormData: {
type: Object as PropType<Spu>, type: Object as PropType<Spu>,

View File

@ -1,4 +1,5 @@
<template> <template>
<!-- 情况一添加/修改 -->
<el-form <el-form
v-if="!isDetail" v-if="!isDetail"
ref="otherSettingsFormRef" ref="otherSettingsFormRef"
@ -56,6 +57,8 @@
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
<!-- 情况二详情 -->
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema"> <Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
<template #recommendHot="{ row }"> <template #recommendHot="{ row }">
{{ row.recommendHot ? '是' : '否' }} {{ row.recommendHot ? '是' : '否' }}

View File

@ -1,4 +1,5 @@
<template> <template>
<!-- 情况一添加/修改 -->
<el-table <el-table
v-if="!isDetail" v-if="!isDetail"
:data="isBatch ? skuList : formData!.skus" :data="isBatch ? skuList : formData!.skus"
@ -109,6 +110,8 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<!-- 情况二详情 -->
<el-table <el-table
v-if="isDetail" v-if="isDetail"
:data="formData!.skus" :data="formData!.skus"
@ -132,7 +135,6 @@
min-width="80" min-width="80"
> >
<template #default="{ row }"> <template #default="{ row }">
<!-- TODO puhui999展示成蓝色有点区分度哈 fix-->
<span style="font-weight: bold; color: #40aaff"> <span style="font-weight: bold; color: #40aaff">
{{ row.properties[index]?.valueName }} {{ row.properties[index]?.valueName }}
</span> </span>

View File

@ -1,5 +1,6 @@
import { CrudSchema } from '@/hooks/web/useCrudSchemas' import { CrudSchema } from '@/hooks/web/useCrudSchemas'
// TODO @puhui999如果只要 detail可以不用 CrudSchema只要描述的 Schema
export const basicInfoSchema = reactive<CrudSchema[]>([ export const basicInfoSchema = reactive<CrudSchema[]>([
{ {
label: '商品名称', label: '商品名称',

View File

@ -171,7 +171,6 @@
</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详情可以后面点做哈 fix-->
<el-button <el-button
v-hasPermi="['product:spu:update']" v-hasPermi="['product:spu:update']"
link link

View 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>

View 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>

View File

@ -171,7 +171,10 @@ const formRules = reactive({
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }] sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }]
}) })
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
@ -204,9 +207,9 @@ const open = async (type: string, id?: number) => {
} }
item.freePrice = fenToYuan(item.freePrice) item.freePrice = fenToYuan(item.freePrice)
}) })
// //
const areaIds = chargeAreaIds.concat(freeAreaIds) const areaIds = chargeAreaIds.concat(freeAreaIds)
// //
areaCache.value = await getAreaListByIds(areaIds.join(',')) areaCache.value = await getAreaListByIds(areaIds.join(','))
} }
} finally { } finally {
@ -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
//
// TODO @jason
data.templateCharge.forEach((item) => { 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 @jasoncolumnTitleMap
// 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
@ -286,7 +310,7 @@ const initData = async () => {
// } finally { // } finally {
// formLoading.value = false // formLoading.value = false
// } // }
// //
columnTitleMap.set(1, { columnTitleMap.set(1, {
startCountTitle: '首件', startCountTitle: '首件',
extraCountTitle: '续件', extraCountTitle: '续件',
@ -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)
@ -357,10 +382,11 @@ const loadFreeArea = async (node, resolve) => {
} else { } else {
const id = node.data.id const id = node.data.id
const data = await getChildrenArea(id) const data = await getChildrenArea(id)
// //
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

View File

@ -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

View File

@ -51,7 +51,7 @@
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="门店所在地区" prop="areaId"> <el-form-item label="门店所在地区" prop="areaId">
<el-cascader v-model="formData.areaId" :options="areaList" :props="cascaderProps" /> <el-cascader v-model="formData.areaId" :options="areaList" :props="areaTreeProps" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
@ -99,7 +99,7 @@
</el-col> </el-col>
</el-row> </el-row>
<el-form-item label="获取经纬度"> <el-form-item label="获取经纬度">
<el-button type="primary" @click="searchLocation">获取</el-button> <el-button type="primary" @click="mapDialogVisible.value = true">获取</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
@ -125,6 +125,7 @@ import { getAreaTree } from '@/api/system/area'
import * as ConfigApi from '@/api/infra/config' import * as ConfigApi from '@/api/infra/config'
const { t } = useI18n() // const { t } = useI18n() //
const message = useMessage() // const message = useMessage() //
const dialogVisible = ref(false) // const dialogVisible = ref(false) //
const mapDialogVisible = ref(false) // const mapDialogVisible = ref(false) //
const dialogTitle = ref('') // const dialogTitle = ref('') //
@ -160,14 +161,15 @@ const formRules = reactive({
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }] status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
}) })
const formRef = ref() // Ref const formRef = ref() // Ref
const cascaderProps = { const areaTreeProps = {
children: 'children', children: 'children',
label: 'name', label: 'name',
value: 'id', value: 'id',
emitPath: false emitPath: false
} }
const areaList = ref() // const areaList = ref() //
const tencentLbsUrl = ref('') // url const tencentLbsUrl = ref('') // url
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, id?: number) => { const open = async (type: string, id?: number) => {
dialogVisible.value = true dialogVisible.value = true
@ -196,7 +198,6 @@ const submitForm = async () => {
// //
formLoading.value = true formLoading.value = true
try { try {
console.log('formData.value', formData.value)
const data = formData.value as DeliveryPickUpStoreApi.DeliveryPickUpStoreVO const data = formData.value as DeliveryPickUpStoreApi.DeliveryPickUpStoreVO
if (formType.value === 'create') { if (formType.value === 'create') {
await DeliveryPickUpStoreApi.createDeliveryPickUpStore(data) await DeliveryPickUpStoreApi.createDeliveryPickUpStore(data)
@ -222,7 +223,7 @@ const resetForm = () => {
logo: '', logo: '',
detailAddress: '', detailAddress: '',
introduction: '', introduction: '',
areaId: 1, areaId: undefined,
openingTime: undefined, openingTime: undefined,
closingTime: undefined, closingTime: undefined,
latitude: undefined, latitude: undefined,
@ -232,11 +233,7 @@ const resetForm = () => {
formRef.value?.resetFields() formRef.value?.resetFields()
} }
// /** 选择经纬度 */
const searchLocation = async () => {
mapDialogVisible.value = true
}
//
const selectAddress = function (loc: any): void { const selectAddress = function (loc: any): void {
if (loc.latlng && loc.latlng.lat) { if (loc.latlng && loc.latlng.lat) {
formData.value.latitude = loc.latlng.lat formData.value.latitude = loc.latlng.lat
@ -246,6 +243,7 @@ const selectAddress = function (loc: any): void {
} }
mapDialogVisible.value = false mapDialogVisible.value = false
} }
/** 初始化数据 */ /** 初始化数据 */
const initData = async () => { const initData = async () => {
formLoading.value = true formLoading.value = true
@ -255,6 +253,7 @@ const initData = async () => {
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
// TODO @jason initTencentLbsMap
window.selectAddress = selectAddress window.selectAddress = selectAddress
window.addEventListener( window.addEventListener(
'message', 'message',
@ -262,7 +261,7 @@ const initData = async () => {
// //
let loc = event.data let loc = event.data
if (loc && loc.module === 'locationPicker') { if (loc && loc.module === 'locationPicker') {
// postmodule'locationPicker' // post module 'locationPicker'
window.parent.selectAddress(loc) window.parent.selectAddress(loc)
} }
}, },

View File

@ -121,6 +121,7 @@ import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
const message = useMessage() // const message = useMessage() //
const { t } = useI18n() // const { t } = useI18n() //
const total = ref(0) // const total = ref(0) //
const loading = ref(true) // const loading = ref(true) //
const exportLoading = ref(false) // const exportLoading = ref(false) //
@ -134,6 +135,7 @@ const queryParams = reactive({
createTime: [] createTime: []
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref() const formRef = ref()
const openForm = (type: string, id?: number) => { const openForm = (type: string, id?: number) => {
@ -153,7 +155,7 @@ const handleDelete = async (id: number) => {
} catch {} } catch {}
} }
//** */ /** 查询列表 */
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
try { try {