Merge remote-tracking branch 'refs/remotes/yudao/dev' into dev-crm

This commit is contained in:
puhui999 2024-05-06 02:12:39 +08:00
commit bcf2f7a5dc
7 changed files with 286 additions and 232 deletions

View File

@ -1,49 +1,48 @@
import request from '@/config/axios' import request from '@/config/axios'
// 秒杀时段 VO
export interface SeckillConfigVO { export interface SeckillConfigVO {
id: number id: number // 编号
name: string name: string // 秒杀时段名称
startTime: string startTime: string // 开始时间点
endTime: string endTime: string // 结束时间点
sliderPicUrls: string[] sliderPicUrls: string[] // 秒杀轮播图
status: number status: number // 活动状态
} }
// 查询秒杀时段配置列表 // 秒杀时段 API
export const getSeckillConfigPage = async (params) => { export const SeckillConfigApi = {
return await request.get({ url: '/promotion/seckill-config/page', params }) // 查询秒杀时段分页
} getSeckillConfigPage: async (params: any) => {
return await request.get({ url: `/promotion/seckill-config/page`, params })
},
// 查询秒杀时段配置详情 // 查询秒杀时段详情
export const getSeckillConfig = async (id: number) => { getSeckillConfig: async (id: number) => {
return await request.get({ url: '/promotion/seckill-config/get?id=' + id }) return await request.get({ url: `/promotion/seckill-config/get?id=` + id })
} },
// 获得所有开启状态的秒杀时段精简列表 // 新增秒杀时段
export const getSimpleSeckillConfigList = async () => { createSeckillConfig: async (data: SeckillConfigVO) => {
return await request.get({ url: '/promotion/seckill-config/list-all-simple' }) return await request.post({ url: `/promotion/seckill-config/create`, data })
} },
// 新增秒杀时段配置 // 修改秒杀时段
export const createSeckillConfig = async (data: SeckillConfigVO) => { updateSeckillConfig: async (data: SeckillConfigVO) => {
return await request.post({ url: '/promotion/seckill-config/create', data }) return await request.put({ url: `/promotion/seckill-config/update`, data })
} },
// 修改秒杀时段配置 // 删除秒杀时段
export const updateSeckillConfig = async (data: SeckillConfigVO) => { deleteSeckillConfig: async (id: number) => {
return await request.put({ url: '/promotion/seckill-config/update', data }) return await request.delete({ url: `/promotion/seckill-config/delete?id=` + id })
} },
// 修改时段配置状态 // 修改时段配置状态
export const updateSeckillConfigStatus = (id: number, status: number) => { updateSeckillConfigStatus: async (id: number, status: number) => {
const data = { const data = {
id, id,
status status
} }
return request.put({ url: '/promotion/seckill-config/update-status', data: data }) return request.put({ url: '/promotion/seckill-config/update-status', data: data })
} }
// 删除秒杀时段配置
export const deleteSeckillConfig = async (id: number) => {
return await request.delete({ url: '/promotion/seckill-config/delete?id=' + id })
} }

View File

@ -17,7 +17,11 @@ export const useUpload = () => {
// 1.2 获取文件预签名地址 // 1.2 获取文件预签名地址
const presignedInfo = await FileApi.getFilePresignedUrl(fileName) const presignedInfo = await FileApi.getFilePresignedUrl(fileName)
// 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传Minio 不支持) // 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传Minio 不支持)
return axios.put(presignedInfo.uploadUrl, options.file).then(() => { return axios.put(presignedInfo.uploadUrl, options.file, {
headers: {
'Content-Type': options.file.type,
}
}).then(() => {
// 1.4. 记录文件信息到后端(异步) // 1.4. 记录文件信息到后端(异步)
createFile(presignedInfo, fileName, options.file) createFile(presignedInfo, fileName, options.file)
// 通知成功,数据格式保持与后端上传的返回结果一致 // 通知成功,数据格式保持与后端上传的返回结果一致

View File

@ -54,7 +54,7 @@
</template> </template>
<div class="max-h-80 overflow-auto"> <div class="max-h-80 overflow-auto">
<ul> <ul>
<li v-for="msg in messageList.reverse()" :key="msg.time" class="mt-2"> <li v-for="msg in messageReverseList" :key="msg.time" class="mt-2">
<div class="flex items-center"> <div class="flex items-center">
<span class="text-primary mr-2 font-medium">收到消息:</span> <span class="text-primary mr-2 font-medium">收到消息:</span>
<span>{{ formatDate(msg.time) }}</span> <span>{{ formatDate(msg.time) }}</span>
@ -92,6 +92,7 @@ const { status, data, send, close, open } = useWebSocket(server.value, {
/** 监听接收到的数据 */ /** 监听接收到的数据 */
const messageList = ref([] as { time: number; text: string }[]) // const messageList = ref([] as { time: number; text: string }[]) //
const messageReverseList = computed(() => messageList.value.slice().reverse())
watchEffect(() => { watchEffect(() => {
if (!data.value) { if (!data.value) {
return return

View File

@ -9,7 +9,7 @@
tag="今日" tag="今日"
title="销售额" title="销售额"
prefix="¥" prefix="¥"
::decimals="2" :decimals="2"
:value="fenToYuan(orderComparison?.value?.orderPayPrice || 0)" :value="fenToYuan(orderComparison?.value?.orderPayPrice || 0)"
:reference="fenToYuan(orderComparison?.reference?.orderPayPrice || 0)" :reference="fenToYuan(orderComparison?.reference?.orderPayPrice || 0)"
/> />
@ -26,8 +26,8 @@
<ComparisonCard <ComparisonCard
tag="今日" tag="今日"
title="订单量" title="订单量"
:value="fenToYuan(orderComparison?.value?.orderPayCount || 0)" :value="orderComparison?.value?.orderPayCount || 0"
:reference="fenToYuan(orderComparison?.reference?.orderPayCount || 0)" :reference="orderComparison?.reference?.orderPayCount || 0"
/> />
</el-col> </el-col>
<el-col :md="6" :sm="12" :xs="24" :loading="loading"> <el-col :md="6" :sm="12" :xs="24" :loading="loading">

View File

@ -1,15 +1,57 @@
<template> <template>
<Dialog v-model="dialogVisible" :title="dialogTitle"> <Dialog :title="dialogTitle" v-model="dialogVisible">
<Form ref="formRef" v-loading="formLoading" :rules="rules" :schema="allSchemas.formSchema" /> <el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
>
<el-form-item label="秒杀时段名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入秒杀时段名称" />
</el-form-item>
<el-form-item label="开始时间点" prop="startTime">
<el-time-picker
v-model="formData.startTime"
value-format="HH:mm:ss"
placeholder="选择开始时间点"
/>
</el-form-item>
<el-form-item label="结束时间点" prop="endTime">
<el-time-picker
v-model="formData.endTime"
value-format="HH:mm:ss"
placeholder="选择结束时间点"
/>
</el-form-item>
<el-form-item label="秒杀轮播图" prop="sliderPicUrls">
<UploadImgs v-model="formData.sliderPicUrls" placeholder="请输入秒杀轮播图" />
</el-form-item>
<el-form-item label="活动状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer> <template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button> <el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button> <el-button @click="dialogVisible = false"> </el-button>
</template> </template>
</Dialog> </Dialog>
</template> </template>
<script lang="ts" name="SeckillConfigForm" setup> <script setup lang="ts">
import * as SeckillConfigApi from '@/api/mall/promotion/seckill/seckillConfig' import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { allSchemas, rules } from './seckillConfig.data' import { SeckillConfigApi, SeckillConfigVO } from '@/api/mall/promotion/seckill/seckillConfig.ts'
import { CommonStatusEnum } from '@/utils/constants'
/** 秒杀时段 表单 */
defineOptions({ name: 'SeckillConfigForm' })
const { t } = useI18n() // const { t } = useI18n() //
const message = useMessage() // const message = useMessage() //
@ -18,6 +60,20 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // const dialogTitle = ref('') //
const formLoading = ref(false) // 12 const formLoading = ref(false) // 12
const formType = ref('') // create - update - const formType = ref('') // create - update -
const formData = ref({
id: undefined,
name: undefined,
startTime: undefined,
endTime: undefined,
sliderPicUrls: undefined,
status: undefined
})
const formRules = reactive({
name: [{ required: true, message: '秒杀时段名称不能为空', trigger: 'blur' }],
startTime: [{ required: true, message: '开始时间点不能为空', trigger: 'blur' }],
endTime: [{ required: true, message: '结束时间点不能为空', trigger: 'blur' }],
status: [{ required: true, message: '活动状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref const formRef = ref() // Ref
/** 打开弹窗 */ /** 打开弹窗 */
@ -25,15 +81,12 @@ const open = async (type: string, id?: number) => {
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = t('action.' + type) dialogTitle.value = t('action.' + type)
formType.value = type formType.value = type
resetForm()
// //
if (id) { if (id) {
formLoading.value = true formLoading.value = true
try { try {
const data = await SeckillConfigApi.getSeckillConfig(id) formData.value = await SeckillConfigApi.getSeckillConfig(id)
data.sliderPicUrls = data['sliderPicUrls']?.map((item) => ({
url: item
}))
formRef.value.setValues(data)
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
@ -45,24 +98,11 @@ defineExpose({ open }) // 提供 open 方法,用于打开弹窗
const emit = defineEmits(['success']) // success const emit = defineEmits(['success']) // success
const submitForm = async () => { const submitForm = async () => {
// //
if (!formRef) return await formRef.value.validate()
const valid = await formRef.value.getElFormRef().validate()
if (!valid) return
// //
formLoading.value = true formLoading.value = true
try { try {
// const data = formData.value as unknown as SeckillConfigVO
const sliderPicUrls = []
formRef.value.formModel.sliderPicUrls.forEach((item) => {
//
typeof item === 'object' ? sliderPicUrls.push(item.url) : sliderPicUrls.push(item)
})
//
const data = {
...formRef.value.formModel,
sliderPicUrls
} as SeckillConfigApi.SeckillConfigVO
if (formType.value === 'create') { if (formType.value === 'create') {
await SeckillConfigApi.createSeckillConfig(data) await SeckillConfigApi.createSeckillConfig(data)
message.success(t('common.createSuccess')) message.success(t('common.createSuccess'))
@ -77,4 +117,17 @@ const submitForm = async () => {
formLoading.value = false formLoading.value = false
} }
} }
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
startTime: undefined,
endTime: undefined,
sliderPicUrls: [],
status: CommonStatusEnum.ENABLE
}
formRef.value?.resetFields()
}
</script> </script>

View File

@ -1,94 +1,172 @@
<template> <template>
<doc-alert title="【营销】秒杀活动" url="https://doc.iocoder.cn/mall/promotion-seckill/" /> <doc-alert title="【营销】秒杀活动" url="https://doc.iocoder.cn/mall/promotion-seckill/" />
<!-- 搜索工作栏 -->
<ContentWrap> <ContentWrap>
<Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams"> <!-- 搜索工作栏 -->
<!-- 新增等操作按钮 --> <el-form
<template #actionMore> class="-mb-15px"
<el-button :model="queryParams"
v-hasPermi="['promotion:seckill-config:create']" ref="queryFormRef"
plain :inline="true"
type="primary" label-width="108px"
@click="openForm('create')"
> >
<Icon class="mr-5px" icon="ep:plus" /> <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>
<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="['promotion:seckill-config:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
</template> </el-form-item>
</Search> </el-form>
</ContentWrap> </ContentWrap>
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<Table <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
v-model:currentPage="tableObject.currentPage" <el-table-column label="秒杀时段名称" align="center" prop="name" />
v-model:pageSize="tableObject.pageSize" <el-table-column label="开始时间点" align="center" prop="startTime" />
:columns="allSchemas.tableColumns" <el-table-column label="结束时间点" align="center" prop="endTime" />
:data="tableObject.tableList" <el-table-column label="秒杀轮播图" align="center" prop="sliderPicUrls">
:loading="tableObject.loading" <template #default="scope">
:pagination="{
total: tableObject.total
}"
>
<template #sliderPicUrls="{ row }">
<el-image <el-image
v-for="(item, index) in row.sliderPicUrls" class="h-40px max-w-40px"
v-for="(url, index) in scope?.row.sliderPicUrls"
:key="index" :key="index"
:src="item" :src="url"
class="mr-10px h-60px w-60px" :preview-src-list="scope?.row.sliderPicUrls"
@click="imagePreview(row.sliderPicUrls)" :initial-index="index"
preview-teleported
/> />
</template> </template>
<template #status="{ row }"> </el-table-column>
<el-table-column label="活动状态" align="center" prop="status">
<template #default="scope">
<el-switch <el-switch
v-model="row.status" v-model="scope.row.status"
:active-value="0" :active-value="0"
:inactive-value="1" :inactive-value="1"
@change="handleStatusChange(row)" @change="handleStatusChange(scope.row)"
/> />
</template> </template>
<template #action="{ row }"> </el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button <el-button
v-hasPermi="['promotion:seckill-config:update']"
link link
type="primary" type="primary"
@click="openForm('update', row.id)" @click="openForm('update', scope.row.id)"
v-hasPermi="['promotion:seckill-config:update']"
> >
编辑 编辑
</el-button> </el-button>
<el-button <el-button
v-hasPermi="['promotion:seckill-config:delete']"
link link
type="danger" type="danger"
@click="handleDelete(row.id)" @click="handleDelete(scope.row.id)"
v-hasPermi="['promotion:seckill-config:delete']"
> >
删除 删除
</el-button> </el-button>
</template> </template>
</Table> </el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap> </ContentWrap>
<!-- 表单弹窗添加/修改 --> <!-- 表单弹窗添加/修改 -->
<SeckillConfigForm ref="formRef" @success="getList" /> <SeckillConfigForm ref="formRef" @success="getList" />
</template> </template>
<script lang="ts" name="PromotionSeckillConfig" setup>
import { allSchemas } from './seckillConfig.data' <script setup lang="ts">
import * as SeckillConfigApi from '@/api/mall/promotion/seckill/seckillConfig' import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { SeckillConfigApi, SeckillConfigVO } from '@/api/mall/promotion/seckill/seckillConfig.ts'
import SeckillConfigForm from './SeckillConfigForm.vue' import SeckillConfigForm from './SeckillConfigForm.vue'
import { createImageViewer } from '@/components/ImageViewer'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
/** 秒杀时段 列表 */
defineOptions({ name: 'SeckillConfig' })
const message = useMessage() // const message = useMessage() //
// tableObject const { t } = useI18n() //
// tableMethods
// https://doc.iocoder.cn/vue3/crud-schema/ const loading = ref(true) //
const { tableObject, tableMethods } = useTable({ const list = ref<SeckillConfigVO[]>([]) //
getListApi: SeckillConfigApi.getSeckillConfigPage, // const total = ref(0) //
delListApi: SeckillConfigApi.deleteSeckillConfig // const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
status: undefined
}) })
// const queryFormRef = ref() //
const { getList, setSearchParams } = tableMethods const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await SeckillConfigApi.getSeckillConfigPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref() const formRef = ref()
@ -97,16 +175,24 @@ const openForm = (type: string, id?: number) => {
} }
/** 删除按钮操作 */ /** 删除按钮操作 */
const handleDelete = (id: number) => { const handleDelete = async (id: number) => {
tableMethods.delList(id, false) try {
//
await message.delConfirm()
//
await SeckillConfigApi.deleteSeckillConfig(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
} }
/** 修改用户状态 */ /** 修改用户状态 */
const handleStatusChange = async (row: SeckillConfigApi.SeckillConfigVO) => { const handleStatusChange = async (row: SeckillConfigVO) => {
try { try {
// //
const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
await message.confirm('确认要"' + text + '""' + row.name + '?') await message.confirm('确认要' + text + '"' + row.name + '"活动吗?')
// //
await SeckillConfigApi.updateSeckillConfigStatus(row.id, row.status) await SeckillConfigApi.updateSeckillConfigStatus(row.id, row.status)
// //
@ -118,13 +204,6 @@ const handleStatusChange = async (row: SeckillConfigApi.SeckillConfigVO) => {
} }
} }
/** 轮播图预览预览 */
const imagePreview = (args) => {
createImageViewer({
urlList: args
})
}
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(() => {
getList() getList()

View File

@ -1,82 +0,0 @@
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
import { dateFormatter } from '@/utils/formatTime'
// 表单校验
export const rules = reactive({
name: [required],
startTime: [required],
endTime: [required],
picUrl: [required],
status: [required]
})
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
const crudSchemas = reactive<CrudSchema[]>([
{
label: '秒杀时段名称',
field: 'name',
isSearch: true
},
{
label: '开始时间点',
field: 'startTime',
isSearch: false,
search: {
component: 'TimePicker'
},
form: {
component: 'TimePicker',
componentProps: {
valueFormat: 'HH:mm:ss'
}
}
},
{
label: '结束时间点',
field: 'endTime',
isSearch: false,
search: {
component: 'TimePicker'
},
form: {
component: 'TimePicker',
componentProps: {
valueFormat: 'HH:mm:ss'
}
}
},
{
label: '秒杀轮播图',
field: 'sliderPicUrls',
isSearch: false,
form: {
component: 'UploadImgs'
},
table: {
width: 300
}
},
{
label: '状态',
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
dictClass: 'number',
isSearch: true,
form: {
component: 'Radio'
}
},
{
label: '创建时间',
field: 'createTime',
isForm: false,
isSearch: false,
formatter: dateFormatter
},
{
label: '操作',
field: 'action',
isForm: false
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)