Commit 7a6c6949 by YunaiV

Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev

parents 989e3081 0db57da9
...@@ -18,6 +18,7 @@ export interface SeckillActivityVO { ...@@ -18,6 +18,7 @@ export interface SeckillActivityVO {
singleLimitCount?: number singleLimitCount?: number
stock?: number stock?: number
totalStock?: number totalStock?: number
seckillPrice?: number
products?: SeckillProductVO[] products?: SeckillProductVO[]
} }
...@@ -43,6 +44,11 @@ export const getSeckillActivityPage = async (params) => { ...@@ -43,6 +44,11 @@ export const getSeckillActivityPage = async (params) => {
return await request.get({ url: '/promotion/seckill-activity/page', params }) return await request.get({ url: '/promotion/seckill-activity/page', params })
} }
// 查询秒杀活动列表,基于活动编号数组
export const getSeckillActivityListByIds = (ids: number[]) => {
return request.get({ url: `/promotion/seckill-activity/list-by-ids?ids=${ids}` })
}
// 查询秒杀活动详情 // 查询秒杀活动详情
export const getSeckillActivity = async (id: number) => { export const getSeckillActivity = async (id: number) => {
return await request.get({ url: '/promotion/seckill-activity/get?id=' + id }) return await request.get({ url: '/promotion/seckill-activity/get?id=' + id })
......
...@@ -44,7 +44,7 @@ defineOptions({ name: 'FloatingActionButton' }) ...@@ -44,7 +44,7 @@ defineOptions({ name: 'FloatingActionButton' })
defineProps<{ property: FloatingActionButtonProperty }>() defineProps<{ property: FloatingActionButtonProperty }>()
// 是否展开 // 是否展开
const expanded = ref(true) const expanded = ref(false)
// 处理展开/折叠 // 处理展开/折叠
const handleToggleFab = () => { const handleToggleFab = () => {
expanded.value = !expanded.value expanded.value = !expanded.value
......
...@@ -3,13 +3,21 @@ import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util' ...@@ -3,13 +3,21 @@ import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 秒杀属性 */ /** 秒杀属性 */
export interface PromotionSeckillProperty { export interface PromotionSeckillProperty {
// 布局类型:单列 | 三列 // 布局类型:单列 | 三列
layoutType: 'oneCol' | 'threeCol' layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'
// 商品字段 // 商品字段
fields: { fields: {
// 商品名称 // 商品名称
name: PromotionSeckillFieldProperty name: PromotionSeckillFieldProperty
// 商品简介
introduction: PromotionSeckillFieldProperty
// 商品价格 // 商品价格
price: PromotionSeckillFieldProperty price: PromotionSeckillFieldProperty
// 市场价
marketPrice: PromotionSeckillFieldProperty
// 商品销量
salesCount: PromotionSeckillFieldProperty
// 商品库存
stock: PromotionSeckillFieldProperty
} }
// 角标 // 角标
badge: { badge: {
...@@ -18,6 +26,19 @@ export interface PromotionSeckillProperty { ...@@ -18,6 +26,19 @@ export interface PromotionSeckillProperty {
// 角标图片 // 角标图片
imgUrl: string imgUrl: string
} }
// 按钮
btnBuy: {
// 类型:文字 | 图片
type: 'text' | 'img'
// 文字
text: string
// 文字按钮:背景渐变起始颜色
bgBeginColor: string
// 文字按钮:背景渐变结束颜色
bgEndColor: string
// 图片按钮:图片地址
imgUrl: string
}
// 上圆角 // 上圆角
borderRadiusTop: number borderRadiusTop: number
// 下圆角 // 下圆角
...@@ -25,10 +46,11 @@ export interface PromotionSeckillProperty { ...@@ -25,10 +46,11 @@ export interface PromotionSeckillProperty {
// 间距 // 间距
space: number space: number
// 秒杀活动编号 // 秒杀活动编号
activityId: number activityIds: number[]
// 组件样式 // 组件样式
style: ComponentStyle style: ComponentStyle
} }
// 商品字段 // 商品字段
export interface PromotionSeckillFieldProperty { export interface PromotionSeckillFieldProperty {
// 是否显示 // 是否显示
...@@ -43,13 +65,23 @@ export const component = { ...@@ -43,13 +65,23 @@ export const component = {
name: '秒杀', name: '秒杀',
icon: 'mdi:calendar-time', icon: 'mdi:calendar-time',
property: { property: {
activityId: undefined, layoutType: 'oneColBigImg',
layoutType: 'oneCol',
fields: { fields: {
name: { show: true, color: '#000' }, name: { show: true, color: '#000' },
price: { show: true, color: '#ff3000' } introduction: { show: true, color: '#999' },
price: { show: true, color: '#ff3000' },
marketPrice: { show: true, color: '#c4c4c4' },
salesCount: { show: true, color: '#c4c4c4' },
stock: { show: false, color: '#c4c4c4' }
}, },
badge: { show: false, imgUrl: '' }, badge: { show: false, imgUrl: '' },
btnBuy: {
type: 'text',
text: '立即秒杀',
bgBeginColor: '#FF6000',
bgEndColor: '#FE832A',
imgUrl: ''
},
borderRadiusTop: 8, borderRadiusTop: 8,
borderRadiusBottom: 8, borderRadiusBottom: 8,
space: 8, space: 8,
......
<template> <template>
<el-scrollbar ref="containerRef" class="z-1 min-h-30px" wrap-class="w-full"> <div :class="`box-content min-h-30px w-full flex flex-row flex-wrap`" ref="containerRef">
<!-- 商品网格 -->
<div <div
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
:style="{ :style="{
gridGap: `${property.space}px`, ...calculateSpace(index),
gridTemplateColumns, ...calculateWidth(),
width: scrollbarWidth
}"
class="grid overflow-x-auto"
>
<!-- 商品 -->
<div
v-for="(spu, index) in spuList"
:key="index"
:style="{
borderTopLeftRadius: `${property.borderRadiusTop}px`, borderTopLeftRadius: `${property.borderRadiusTop}px`,
borderTopRightRadius: `${property.borderRadiusTop}px`, borderTopRightRadius: `${property.borderRadiusTop}px`,
borderBottomLeftRadius: `${property.borderRadiusBottom}px`, borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
borderBottomRightRadius: `${property.borderRadiusBottom}px` borderBottomRightRadius: `${property.borderRadiusBottom}px`
}" }"
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white" v-for="(spu, index) in spuList"
:key="index"
> >
<!-- 角标 --> <!-- 角标 -->
<div v-if="property.badge.show" class="absolute left-0 top-0 z-1 items-center justify-center">
<el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
</div>
<!-- 商品封面图 -->
<div <div
v-if="property.badge.show" :class="[
class="absolute left-0 top-0 z-1 items-center justify-center" 'h-140px',
{
'w-full': property.layoutType !== 'oneColSmallImg',
'w-140px': property.layoutType === 'oneColSmallImg'
}
]"
> >
<el-image :src="property.badge.imgUrl" class="h-26px w-38px" fit="cover" /> <el-image fit="cover" class="h-full w-full" :src="spu.picUrl" />
</div> </div>
<!-- 商品封面图 -->
<el-image :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" fit="cover" />
<div <div
:class="[ :class="[
'flex flex-col gap-8px p-8px box-border', ' flex flex-col gap-8px p-8px box-border',
{ {
'w-[calc(100%-64px)]': columns === 2, 'w-full': property.layoutType !== 'oneColSmallImg',
'w-full': columns === 3 'w-[calc(100%-140px-16px)]': property.layoutType === 'oneColSmallImg'
} }
]" ]"
> >
<!-- 商品名称 --> <!-- 商品名称 -->
<div <div
v-if="property.fields.name.show" v-if="property.fields.name.show"
:class="[
'text-14px ',
{
truncate: property.layoutType !== 'oneColSmallImg',
'overflow-ellipsis line-clamp-2': property.layoutType === 'oneColSmallImg'
}
]"
:style="{ color: property.fields.name.color }" :style="{ color: property.fields.name.color }"
class="truncate text-12px"
> >
{{ spu.name }} {{ spu.name }}
</div> </div>
<!-- 商品简介 -->
<div
v-if="property.fields.introduction.show"
class="truncate text-12px"
:style="{ color: property.fields.introduction.color }"
>
{{ spu.introduction }}
</div>
<div> <div>
<!-- 商品价格 --> <!-- 价格 -->
<span <span
v-if="property.fields.price.show" v-if="property.fields.price.show"
class="text-16px"
:style="{ color: property.fields.price.color }" :style="{ color: property.fields.price.color }"
class="text-12px"
> >
{{ fenToYuan(spu.seckillPrice || spu.price || 0) }} {{ fenToYuan(spu.price || Infinity) }}
</span>
<!-- 市场价 -->
<span
v-if="property.fields.marketPrice.show && spu.marketPrice"
class="ml-4px text-10px line-through"
:style="{ color: property.fields.marketPrice.color }"
>¥{{ fenToYuan(spu.marketPrice) }}</span
>
</div>
<div class="text-12px">
<!-- 销量 -->
<span
v-if="property.fields.salesCount.show"
:style="{ color: property.fields.salesCount.color }"
>
已售{{ (spu.salesCount || 0) + (spu.virtualSalesCount || 0) }}
</span>
<!-- 库存 -->
<span v-if="property.fields.stock.show" :style="{ color: property.fields.stock.color }">
库存{{ spu.stock || 0 }}
</span> </span>
</div> </div>
</div> </div>
<!-- 购买按钮 -->
<div class="absolute bottom-8px right-8px">
<!-- 文字按钮 -->
<span
v-if="property.btnBuy.type === 'text'"
class="rounded-full p-x-12px p-y-4px text-12px text-white"
:style="{
background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`
}"
>
{{ property.btnBuy.text }}
</span>
<!-- 图片按钮 -->
<el-image
v-else
class="h-28px w-28px rounded-full"
fit="cover"
:src="property.btnBuy.imgUrl"
/>
</div>
</div> </div>
</div> </div>
</el-scrollbar>
</template> </template>
<script lang="ts" setup> <script setup lang="ts">
import { PromotionSeckillProperty } from './config' import { PromotionSeckillProperty } from './config'
import * as ProductSpuApi from '@/api/mall/product/spu' import * as ProductSpuApi from '@/api/mall/product/spu'
import { Spu } from '@/api/mall/product/spu'
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity' import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
import { SeckillProductVO } from '@/api/mall/promotion/seckill/seckillActivity'
import { fenToYuan } from '@/utils' import { fenToYuan } from '@/utils'
/** 秒杀 */ /** 秒杀卡片 */
defineOptions({ name: 'PromotionSeckill' }) defineOptions({ name: 'PromotionSeckill' })
// 定义属性 // 定义属性
const props = defineProps<{ property: PromotionSeckillProperty }>() const props = defineProps<{ property: PromotionSeckillProperty }>()
// 商品列表 // 商品列表
const spuList = ref<ProductSpuApi.Spu[]>([]) const spuList = ref<ProductSpuApi.Spu[]>([])
const spuIdList = ref<number[]>([])
const seckillActivityList = ref<SeckillActivityApi.SeckillActivityVO[]>([])
watch( watch(
() => props.property.activityId, () => props.property.activityIds,
async () => { async () => {
if (!props.property.activityId) return try {
const activity = await SeckillActivityApi.getSeckillActivity(props.property.activityId) // 新添加的秒杀组件,是没有活动ID的
if (!activity?.spuId) return const activityIds = props.property.activityIds
spuList.value = [await ProductSpuApi.getSpu(activity.spuId)] // 检查活动ID的有效性
spuList.value = [await ProductSpuApi.getSpu(activity.spuId)] if (Array.isArray(activityIds) && activityIds.length > 0) {
// 循环活动信息,赋值秒杀最低价格 // 获取秒杀活动详情列表
activity.products.forEach((product: SeckillProductVO) => { seckillActivityList.value =
spuList.value.forEach((spu: Spu) => { await SeckillActivityApi.getSeckillActivityListByIds(activityIds)
spu.seckillPrice = Math.min(spu.seckillPrice || Infinity, product.seckillPrice) // 设置 SPU 的最低价格
}) // 获取秒杀活动的 SPU 详情列表
spuList.value = []
spuIdList.value = seckillActivityList.value
.map((activity) => activity.spuId)
.filter((spuId): spuId is number => typeof spuId === 'number')
if (spuIdList.value.length > 0) {
spuList.value = await ProductSpuApi.getSpuDetailList(spuIdList.value)
}
// 更新 SPU 的最低价格
seckillActivityList.value.forEach((activity) => {
// 匹配spuId
const spu = spuList.value.find((spu) => spu.id === activity.spuId)
if (spu) {
// 赋值活动价格,哪个最便宜就赋值哪个
spu.price = Math.min(activity.seckillPrice || Infinity, spu.price || Infinity)
}
}) })
}
} catch (error) {
console.error('获取秒杀活动细节或 SPU 细节时出错:', error)
}
}, },
{ {
immediate: true, immediate: true,
deep: true deep: true
} }
) )
// 手机宽度
const phoneWidth = ref(375) /**
* 计算商品的间距
* @param index 商品索引
*/
const calculateSpace = (index: number) => {
// 商品的列数
const columns = props.property.layoutType === 'twoCol' ? 2 : 1
// 第一列没有左边距
const marginLeft = index % columns === 0 ? '0' : props.property.space + 'px'
// 第一行没有上边距
const marginTop = index < columns ? '0' : props.property.space + 'px'
return { marginLeft, marginTop }
}
// 容器 // 容器
const containerRef = ref() const containerRef = ref()
// 商品的列数 // 计算商品的宽度
const columns = ref(2) const calculateWidth = () => {
// 滚动条宽度 let width = '100%'
const scrollbarWidth = ref('100%') // 双列时每列的宽度为:(总宽度 - 间距)/ 2
// 商品图大小 if (props.property.layoutType === 'twoCol') {
const imageSize = ref('0') width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`
// 商品网络列数 }
const gridTemplateColumns = ref('') return { width }
// 计算布局参数 }
watch(
() => [props.property, phoneWidth, spuList.value.length],
() => {
// 计算列数
columns.value = props.property.layoutType === 'oneCol' ? 1 : 3
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
const productWidth =
(phoneWidth.value - props.property.space * (columns.value - 1)) / columns.value
// 商品图布局:2列时,左右布局 3列时,上下布局
imageSize.value = columns.value === 2 ? '64px' : `${productWidth}px`
// 指定列数
gridTemplateColumns.value = `repeat(${columns.value}, auto)`
// 不滚动
scrollbarWidth.value = '100%'
},
{ immediate: true, deep: true }
)
onMounted(() => {
// 提取手机宽度
phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375
})
</script> </script>
<style lang="scss" scoped></style> <style scoped lang="scss"></style>
...@@ -2,30 +2,31 @@ ...@@ -2,30 +2,31 @@
<ComponentContainerProperty v-model="formData.style"> <ComponentContainerProperty v-model="formData.style">
<el-form label-width="80px" :model="formData"> <el-form label-width="80px" :model="formData">
<el-card header="秒杀活动" class="property-group" shadow="never"> <el-card header="秒杀活动" class="property-group" shadow="never">
<el-form-item label="秒杀活动" prop="activityId"> <SeckillShowcase v-model="formData.activityIds" />
<el-select v-model="formData.activityId">
<el-option
v-for="activity in activityList"
:key="activity.id"
:label="activity.name"
:value="activity.id"
/>
</el-select>
</el-form-item>
</el-card> </el-card>
<el-card header="商品样式" class="property-group" shadow="never"> <el-card header="商品样式" class="property-group" shadow="never">
<el-form-item label="布局" prop="type"> <el-form-item label="布局" prop="type">
<el-radio-group v-model="formData.layoutType"> <el-radio-group v-model="formData.layoutType">
<el-tooltip class="item" content="单列" placement="bottom"> <el-tooltip class="item" content="单列大图" placement="bottom">
<el-radio-button value="oneCol"> <el-radio-button value="oneColBigImg">
<Icon icon="fluent:text-column-one-24-filled" /> <Icon icon="fluent:text-column-one-24-filled" />
</el-radio-button> </el-radio-button>
</el-tooltip> </el-tooltip>
<el-tooltip class="item" content="三列" placement="bottom"> <el-tooltip class="item" content="单列小图" placement="bottom">
<el-radio-button value="oneColSmallImg">
<Icon icon="fluent:text-column-two-left-24-filled" />
</el-radio-button>
</el-tooltip>
<el-tooltip class="item" content="双列" placement="bottom">
<el-radio-button value="twoCol">
<Icon icon="fluent:text-column-two-24-filled" />
</el-radio-button>
</el-tooltip>
<!--<el-tooltip class="item" content="三列" placement="bottom">
<el-radio-button value="threeCol"> <el-radio-button value="threeCol">
<Icon icon="fluent:text-column-three-24-filled" /> <Icon icon="fluent:text-column-three-24-filled" />
</el-radio-button> </el-radio-button>
</el-tooltip> </el-tooltip>-->
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="商品名称" prop="fields.name.show"> <el-form-item label="商品名称" prop="fields.name.show">
...@@ -34,12 +35,36 @@ ...@@ -34,12 +35,36 @@
<el-checkbox v-model="formData.fields.name.show" /> <el-checkbox v-model="formData.fields.name.show" />
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="商品简介" prop="fields.introduction.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.introduction.color" />
<el-checkbox v-model="formData.fields.introduction.show" />
</div>
</el-form-item>
<el-form-item label="商品价格" prop="fields.price.show"> <el-form-item label="商品价格" prop="fields.price.show">
<div class="flex gap-8px"> <div class="flex gap-8px">
<ColorInput v-model="formData.fields.price.color" /> <ColorInput v-model="formData.fields.price.color" />
<el-checkbox v-model="formData.fields.price.show" /> <el-checkbox v-model="formData.fields.price.show" />
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="市场价" prop="fields.marketPrice.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.marketPrice.color" />
<el-checkbox v-model="formData.fields.marketPrice.show" />
</div>
</el-form-item>
<el-form-item label="商品销量" prop="fields.salesCount.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.salesCount.color" />
<el-checkbox v-model="formData.fields.salesCount.show" />
</div>
</el-form-item>
<el-form-item label="商品库存" prop="fields.stock.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.stock.color" />
<el-checkbox v-model="formData.fields.stock.show" />
</div>
</el-form-item>
</el-card> </el-card>
<el-card header="角标" class="property-group" shadow="never"> <el-card header="角标" class="property-group" shadow="never">
<el-form-item label="角标" prop="badge.show"> <el-form-item label="角标" prop="badge.show">
...@@ -47,9 +72,35 @@ ...@@ -47,9 +72,35 @@
</el-form-item> </el-form-item>
<el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show"> <el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px"> <UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
<template #tip> 建议尺寸:36 * 22 </template> <template #tip> 建议尺寸:36 * 22</template>
</UploadImg>
</el-form-item>
</el-card>
<el-card header="按钮" class="property-group" shadow="never">
<el-form-item label="按钮类型" prop="btnBuy.type">
<el-radio-group v-model="formData.btnBuy.type">
<el-radio-button value="text">文字</el-radio-button>
<el-radio-button value="img">图片</el-radio-button>
</el-radio-group>
</el-form-item>
<template v-if="formData.btnBuy.type === 'text'">
<el-form-item label="按钮文字" prop="btnBuy.text">
<el-input v-model="formData.btnBuy.text" />
</el-form-item>
<el-form-item label="左侧背景" prop="btnBuy.bgBeginColor">
<ColorInput v-model="formData.btnBuy.bgBeginColor" />
</el-form-item>
<el-form-item label="右侧背景" prop="btnBuy.bgEndColor">
<ColorInput v-model="formData.btnBuy.bgEndColor" />
</el-form-item>
</template>
<template v-else>
<el-form-item label="图片" prop="btnBuy.imgUrl">
<UploadImg v-model="formData.btnBuy.imgUrl" height="56px" width="56px">
<template #tip> 建议尺寸:56 * 56</template>
</UploadImg> </UploadImg>
</el-form-item> </el-form-item>
</template>
</el-card> </el-card>
<el-card header="商品样式" class="property-group" shadow="never"> <el-card header="商品样式" class="property-group" shadow="never">
<el-form-item label="上圆角" prop="borderRadiusTop"> <el-form-item label="上圆角" prop="borderRadiusTop">
...@@ -92,6 +143,7 @@ import { PromotionSeckillProperty } from './config' ...@@ -92,6 +143,7 @@ import { PromotionSeckillProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { usePropertyForm } from '@/components/DiyEditor/util'
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity' import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
import SeckillShowcase from '@/views/mall/promotion/seckill/components/SeckillShowcase.vue'
// 秒杀属性面板 // 秒杀属性面板
defineOptions({ name: 'PromotionSeckillProperty' }) defineOptions({ name: 'PromotionSeckillProperty' })
...@@ -100,7 +152,7 @@ const props = defineProps<{ modelValue: PromotionSeckillProperty }>() ...@@ -100,7 +152,7 @@ const props = defineProps<{ modelValue: PromotionSeckillProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const { formData } = usePropertyForm(props.modelValue, emit)
// 活动列表 // 活动列表
const activityList = ref<SeckillActivityApi.SeckillActivityVO>([]) const activityList = ref<SeckillActivityApi.SeckillActivityVO[]>([])
onMounted(async () => { onMounted(async () => {
const { list } = await SeckillActivityApi.getSeckillActivityPage({ const { list } = await SeckillActivityApi.getSeckillActivityPage({
status: CommonStatusEnum.ENABLE status: CommonStatusEnum.ENABLE
......
...@@ -48,7 +48,7 @@ export const useDictSelectRule = () => { ...@@ -48,7 +48,7 @@ export const useDictSelectRule = () => {
}, },
{ {
type: 'select', type: 'select',
field: 'dictValueType', field: 'valueType',
title: '字典值类型', title: '字典值类型',
value: 'str', value: 'str',
options: [ options: [
......
...@@ -5,7 +5,7 @@ import remainingRouter from './modules/remaining' ...@@ -5,7 +5,7 @@ import remainingRouter from './modules/remaining'
// 创建路由实例 // 创建路由实例
const router = createRouter({ const router = createRouter({
history: createWebHistory(), // createWebHashHistory URL带#,createWebHistory URL不带# history: createWebHistory(import.meta.env.VITE_BASE_PATH), // createWebHashHistory URL带#,createWebHistory URL不带#
strict: true, strict: true,
routes: remainingRouter as RouteRecordRaw[], routes: remainingRouter as RouteRecordRaw[],
scrollBehavior: () => ({ left: 0, top: 0 }) scrollBehavior: () => ({ left: 0, top: 0 })
......
...@@ -88,7 +88,8 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord ...@@ -88,7 +88,8 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
// 2. 生成 data(AppRouteRecordRaw) // 2. 生成 data(AppRouteRecordRaw)
// 路由地址转首字母大写驼峰,作为路由名称,适配keepAlive // 路由地址转首字母大写驼峰,作为路由名称,适配keepAlive
let data: AppRouteRecordRaw = { let data: AppRouteRecordRaw = {
path: route.path.indexOf('?') > -1 ? route.path.split('?')[0] : route.path, path:
route.path.indexOf('?') > -1 && !isUrl(route.path) ? route.path.split('?')[0] : route.path, // 注意,需要排除 http 这种 url,避免它带 ? 参数被截取掉
name: name:
route.componentName && route.componentName.length > 0 route.componentName && route.componentName.length > 0
? route.componentName ? route.componentName
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
ref="permissionListRef" ref="permissionListRef"
:biz-id="contract.id!" :biz-id="contract.id!"
:biz-type="BizTypeEnum.CRM_CONTRACT" :biz-type="BizTypeEnum.CRM_CONTRACT"
:show-action="!permissionListRef?.isPool || false" :show-action="true"
@quit-team="close" @quit-team="close"
/> />
</el-tab-pane> </el-tab-pane>
......
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
<OrderItem <OrderItem
v-if="KeFuMessageContentTypeEnum.ORDER === item.contentType" v-if="KeFuMessageContentTypeEnum.ORDER === item.contentType"
:message="item" :message="item"
class="max-w-70%" class="max-w-100%"
/> />
</MessageItem> </MessageItem>
</div> </div>
...@@ -423,9 +423,9 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => { ...@@ -423,9 +423,9 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
// 消息气泡 // 消息气泡
.kefu-message { .kefu-message {
color: #A9A9A9; color: #a9a9a9;
border-radius: 5px; border-radius: 5px;
box-shadow: 3px 3px 5px rgba(220,220,220, 0.1); box-shadow: 3px 3px 5px rgba(220, 220, 220, 0.1);
padding: 5px 10px; padding: 5px 10px;
width: auto; width: auto;
max-width: 50%; max-width: 50%;
......
...@@ -66,7 +66,7 @@ export const useEmoji = () => { ...@@ -66,7 +66,7 @@ export const useEmoji = () => {
) )
for (const path in pathList) { for (const path in pathList) {
const imageModule: any = await pathList[path]() const imageModule: any = await pathList[path]()
emojiPathList.value.push(imageModule.default) emojiPathList.value.push({ path: path, src: imageModule.default })
} }
} }
...@@ -116,7 +116,10 @@ export const useEmoji = () => { ...@@ -116,7 +116,10 @@ export const useEmoji = () => {
function getEmojiFileByName(name: string) { function getEmojiFileByName(name: string) {
for (const emoji of emojiList) { for (const emoji of emojiList) {
if (emoji.name === name) { if (emoji.name === name) {
return emojiPathList.value.find((item: string) => item.indexOf(emoji.file) > -1) const emojiPath = emojiPathList.value.find(
(item: { path: string; src: string }) => item.path.indexOf(emoji.file) > -1
)
return emojiPath ? emojiPath.src : undefined
} }
} }
return false return false
......
<template>
<div class="flex flex-wrap items-center gap-8px">
<div
v-for="(seckillActivity, index) in Activitys"
:key="seckillActivity.id"
class="select-box spu-pic"
>
<el-tooltip :content="seckillActivity.name">
<div class="relative h-full w-full">
<el-image :src="seckillActivity.picUrl" class="h-full w-full" />
<Icon
v-show="!disabled"
class="del-icon"
icon="ep:circle-close-filled"
@click="handleRemoveActivity(index)"
/>
</div>
</el-tooltip>
</div>
<el-tooltip content="选择活动" v-if="canAdd">
<div class="select-box" @click="openSeckillActivityTableSelect">
<Icon icon="ep:plus" />
</div>
</el-tooltip>
</div>
<!-- 拼团活动选择对话框(表格形式) -->
<SeckillTableSelect
ref="seckillActivityTableSelectRef"
:multiple="limit != 1"
@change="handleActivitySelected"
/>
</template>
<script lang="ts" setup>
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
import { propTypes } from '@/utils/propTypes'
import { oneOfType } from 'vue-types'
import { isArray } from '@/utils/is'
import SeckillTableSelect from '@/views/mall/promotion/seckill/components/SeckillTableSelect.vue'
// 活动橱窗,一般用于装修时使用
// 提供功能:展示活动列表、添加活动、删除活动
defineOptions({ name: 'SeckillShowcase' })
const props = defineProps({
modelValue: oneOfType<number | Array<number>>([Number, Array]).isRequired,
// 限制数量:默认不限制
limit: propTypes.number.def(Number.MAX_VALUE),
disabled: propTypes.bool.def(false)
})
// 计算是否可以添加
const canAdd = computed(() => {
// 情况一:禁用时不可以添加
if (props.disabled) return false
// 情况二:未指定限制数量时,可以添加
if (!props.limit) return true
// 情况三:检查已添加数量是否小于限制数量
return Activitys.value.length < props.limit
})
// 拼团活动列表
const Activitys = ref<SeckillActivityApi.SeckillActivityVO[]>([])
watch(
() => props.modelValue,
async () => {
const ids = isArray(props.modelValue)
? // 情况一:多选
props.modelValue
: // 情况二:单选
props.modelValue
? [props.modelValue]
: []
// 不需要返显
if (ids.length === 0) {
Activitys.value = []
return
}
// 只有活动发生变化之后,才会查询活动
if (
Activitys.value.length === 0 ||
Activitys.value.some((seckillActivity) => !ids.includes(seckillActivity.id!))
) {
Activitys.value = await SeckillActivityApi.getSeckillActivityListByIds(ids)
}
},
{ immediate: true }
)
/** 活动表格选择对话框 */
const seckillActivityTableSelectRef = ref()
// 打开对话框
const openSeckillActivityTableSelect = () => {
seckillActivityTableSelectRef.value.open(Activitys.value)
}
/**
* 选择活动后触发
* @param activityVOs 选中的活动列表
*/
const handleActivitySelected = (
activityVOs: SeckillActivityApi.SeckillActivityVO | SeckillActivityApi.SeckillActivityVO[]
) => {
Activitys.value = isArray(activityVOs) ? activityVOs : [activityVOs]
emitActivityChange()
}
/**
* 删除活动
* @param index 活动索引
*/
const handleRemoveActivity = (index: number) => {
Activitys.value.splice(index, 1)
emitActivityChange()
}
const emit = defineEmits(['update:modelValue', 'change'])
const emitActivityChange = () => {
if (props.limit === 1) {
const seckillActivity = Activitys.value.length > 0 ? Activitys.value[0] : null
emit('update:modelValue', seckillActivity?.id || 0)
emit('change', seckillActivity)
} else {
emit(
'update:modelValue',
Activitys.value.map((seckillActivity) => seckillActivity.id)
)
emit('change', Activitys.value)
}
}
</script>
<style lang="scss" scoped>
.select-box {
display: flex;
width: 60px;
height: 60px;
border: 1px dashed var(--el-border-color-darker);
border-radius: 8px;
align-items: center;
justify-content: center;
cursor: pointer;
}
.spu-pic {
position: relative;
}
.del-icon {
position: absolute;
top: -10px;
right: -10px;
z-index: 1;
width: 20px !important;
height: 20px !important;
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment