commit
d6ff66dc1d
@ -42,12 +42,16 @@ export const exportDemo03Student = async (params) => {
|
|||||||
|
|
||||||
// 获得学生课程列表
|
// 获得学生课程列表
|
||||||
export const getDemo03CourseListByStudentId = async (studentId) => {
|
export const getDemo03CourseListByStudentId = async (studentId) => {
|
||||||
return await request.get({ url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId })
|
return await request.get({
|
||||||
|
url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 子表(学生班级) ====================
|
// ==================== 子表(学生班级) ====================
|
||||||
|
|
||||||
// 获得学生班级
|
// 获得学生班级
|
||||||
export const getDemo03GradeByStudentId = async (studentId) => {
|
export const getDemo03GradeByStudentId = async (studentId) => {
|
||||||
return await request.get({ url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId })
|
return await request.get({
|
||||||
|
url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId
|
||||||
|
})
|
||||||
}
|
}
|
@ -42,12 +42,16 @@ export const exportDemo03Student = async (params) => {
|
|||||||
|
|
||||||
// 获得学生课程列表
|
// 获得学生课程列表
|
||||||
export const getDemo03CourseListByStudentId = async (studentId) => {
|
export const getDemo03CourseListByStudentId = async (studentId) => {
|
||||||
return await request.get({ url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId })
|
return await request.get({
|
||||||
|
url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 子表(学生班级) ====================
|
// ==================== 子表(学生班级) ====================
|
||||||
|
|
||||||
// 获得学生班级
|
// 获得学生班级
|
||||||
export const getDemo03GradeByStudentId = async (studentId) => {
|
export const getDemo03GradeByStudentId = async (studentId) => {
|
||||||
return await request.get({ url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId })
|
return await request.get({
|
||||||
|
url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId
|
||||||
|
})
|
||||||
}
|
}
|
@ -17,7 +17,7 @@ export interface ArticleVO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 查询文章管理列表
|
// 查询文章管理列表
|
||||||
export const getArticlePage = async (params) => {
|
export const getArticlePage = async (params: any) => {
|
||||||
return await request.get({ url: `/promotion/article/page`, params })
|
return await request.get({ url: `/promotion/article/page`, params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
198
src/components/AppLinkInput/AppLinkSelectDialog.vue
Normal file
198
src/components/AppLinkInput/AppLinkSelectDialog.vue
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible" title="选择链接" width="65%">
|
||||||
|
<div class="h-500px flex gap-8px">
|
||||||
|
<!-- 左侧分组列表 -->
|
||||||
|
<el-scrollbar wrap-class="h-full" ref="groupScrollbar" view-class="flex flex-col">
|
||||||
|
<el-button
|
||||||
|
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
|
||||||
|
:key="groupIndex"
|
||||||
|
:class="[
|
||||||
|
'm-r-16px m-l-0px! justify-start! w-90px',
|
||||||
|
{ active: activeGroup === group.name }
|
||||||
|
]"
|
||||||
|
ref="groupBtnRefs"
|
||||||
|
:text="activeGroup !== group.name"
|
||||||
|
:type="activeGroup === group.name ? 'primary' : 'default'"
|
||||||
|
@click="handleGroupSelected(group.name)"
|
||||||
|
>
|
||||||
|
{{ group.name }}
|
||||||
|
</el-button>
|
||||||
|
</el-scrollbar>
|
||||||
|
<!-- 右侧链接列表 -->
|
||||||
|
<el-scrollbar class="h-full flex-1" @scroll="handleScroll" ref="linkScrollbar">
|
||||||
|
<div v-for="(group, groupIndex) in APP_LINK_GROUP_LIST" :key="groupIndex">
|
||||||
|
<!-- 分组标题 -->
|
||||||
|
<div class="font-bold" ref="groupTitleRefs">{{ group.name }}</div>
|
||||||
|
<!-- 链接列表 -->
|
||||||
|
<el-tooltip
|
||||||
|
v-for="(appLink, appLinkIndex) in group.links"
|
||||||
|
:key="appLinkIndex"
|
||||||
|
:content="appLink.path"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
class="m-b-8px m-r-8px m-l-0px!"
|
||||||
|
:type="isSameLink(appLink.path, activeAppLink) ? 'primary' : 'default'"
|
||||||
|
@click="handleAppLinkSelected(appLink)"
|
||||||
|
>
|
||||||
|
{{ appLink.name }}
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
<!-- 底部对话框操作按钮 -->
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
<Dialog v-model="detailSelectDialog.visible" title="" width="50%">
|
||||||
|
<el-form class="min-h-200px">
|
||||||
|
<el-form-item
|
||||||
|
label="选择分类"
|
||||||
|
v-if="detailSelectDialog.type === APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST"
|
||||||
|
>
|
||||||
|
<ProductCategorySelect
|
||||||
|
v-model="detailSelectDialog.id"
|
||||||
|
:parent-id="0"
|
||||||
|
@update:model-value="handleProductCategorySelected"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { APP_LINK_GROUP_LIST, APP_LINK_TYPE_ENUM } from './data'
|
||||||
|
import { ButtonInstance, ScrollbarInstance } from 'element-plus'
|
||||||
|
import { split } from 'lodash-es'
|
||||||
|
import ProductCategorySelect from '@/views/mall/product/category/components/ProductCategorySelect.vue'
|
||||||
|
import { getUrlNumberValue } from '@/utils'
|
||||||
|
|
||||||
|
// APP 链接选择弹框
|
||||||
|
defineOptions({ name: 'AppLinkSelectDialog' })
|
||||||
|
// 选中的分组,默认选中第一个
|
||||||
|
const activeGroup = ref(APP_LINK_GROUP_LIST[0].name)
|
||||||
|
// 选中的 APP 链接
|
||||||
|
const activeAppLink = ref('')
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const open = (link: string) => {
|
||||||
|
activeAppLink.value = link
|
||||||
|
dialogVisible.value = true
|
||||||
|
|
||||||
|
// 滚动到当前的链接
|
||||||
|
const group = APP_LINK_GROUP_LIST.find((group) =>
|
||||||
|
group.links.some((linkItem) => isSameLink(linkItem.path, link))
|
||||||
|
)
|
||||||
|
if (group) {
|
||||||
|
// 使用 nextTick 的原因:可能 Dom 还没生成,导致滚动失败
|
||||||
|
nextTick(() => handleGroupSelected(group.name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open })
|
||||||
|
|
||||||
|
// 处理 APP 链接选中
|
||||||
|
const handleAppLinkSelected = (appLink: any) => {
|
||||||
|
if (!isSameLink(appLink.path, activeAppLink.value)) {
|
||||||
|
activeAppLink.value = appLink.path
|
||||||
|
}
|
||||||
|
switch (appLink.type) {
|
||||||
|
case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST:
|
||||||
|
detailSelectDialog.value.visible = true
|
||||||
|
detailSelectDialog.value.type = appLink.type
|
||||||
|
// 返显
|
||||||
|
detailSelectDialog.value.id =
|
||||||
|
getUrlNumberValue('id', 'http://127.0.0.1' + activeAppLink.value) || undefined
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理绑定值更新
|
||||||
|
const emit = defineEmits<{
|
||||||
|
change: [link: string]
|
||||||
|
}>()
|
||||||
|
const handleSubmit = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
emit('change', activeAppLink.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分组标题引用列表
|
||||||
|
const groupTitleRefs = ref<HTMLInputElement[]>([])
|
||||||
|
/**
|
||||||
|
* 处理右侧链接列表滚动
|
||||||
|
* @param scrollTop 滚动条的位置
|
||||||
|
*/
|
||||||
|
const handleScroll = ({ scrollTop }: { scrollTop: number }) => {
|
||||||
|
const titleEl = groupTitleRefs.value.find((titleEl) => {
|
||||||
|
// 获取标题的位置信息
|
||||||
|
const { offsetHeight, offsetTop } = titleEl
|
||||||
|
// 判断标题是否在可视范围内
|
||||||
|
return scrollTop >= offsetTop && scrollTop < offsetTop + offsetHeight
|
||||||
|
})
|
||||||
|
// 只需处理一次
|
||||||
|
if (titleEl && activeGroup.value !== titleEl.textContent) {
|
||||||
|
activeGroup.value = titleEl.textContent || ''
|
||||||
|
// 同步左侧的滚动条位置
|
||||||
|
scrollToGroupBtn(activeGroup.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧滚动条
|
||||||
|
const linkScrollbar = ref<ScrollbarInstance>()
|
||||||
|
// 处理分组选中
|
||||||
|
const handleGroupSelected = (group: string) => {
|
||||||
|
activeGroup.value = group
|
||||||
|
const titleRef = groupTitleRefs.value.find((item) => item.textContent === group)
|
||||||
|
if (titleRef) {
|
||||||
|
// 滚动分组标题
|
||||||
|
linkScrollbar.value?.setScrollTop(titleRef.offsetTop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分组滚动条
|
||||||
|
const groupScrollbar = ref<ScrollbarInstance>()
|
||||||
|
// 分组引用列表
|
||||||
|
const groupBtnRefs = ref<ButtonInstance[]>([])
|
||||||
|
// 自动滚动分组按钮,确保分组按钮保持在可视区域内
|
||||||
|
const scrollToGroupBtn = (group: string) => {
|
||||||
|
const groupBtn = groupBtnRefs.value
|
||||||
|
.map((btn) => btn['ref'])
|
||||||
|
.find((ref) => ref.textContent === group)
|
||||||
|
if (groupBtn) {
|
||||||
|
groupScrollbar.value?.setScrollTop(groupBtn.offsetTop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否为相同的链接(不比较参数,只比较链接)
|
||||||
|
const isSameLink = (link1: string, link2: string) => {
|
||||||
|
return split(link1, '?', 1)[0] === split(link2, '?', 1)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 详情选择对话框
|
||||||
|
const detailSelectDialog = ref<{
|
||||||
|
visible: boolean
|
||||||
|
id?: number
|
||||||
|
type?: APP_LINK_TYPE_ENUM
|
||||||
|
}>({
|
||||||
|
visible: false,
|
||||||
|
id: undefined,
|
||||||
|
type: undefined
|
||||||
|
})
|
||||||
|
// 处理详情选择
|
||||||
|
const handleProductCategorySelected = (id: number) => {
|
||||||
|
const url = new URL(activeAppLink.value, 'http://127.0.0.1')
|
||||||
|
// 修改 id 参数
|
||||||
|
url.searchParams.set('id', `${id}`)
|
||||||
|
// 排除域名
|
||||||
|
activeAppLink.value = `${url.pathname}${url.search}`
|
||||||
|
// 关闭对话框
|
||||||
|
detailSelectDialog.value.visible = false
|
||||||
|
// 重置 id
|
||||||
|
detailSelectDialog.value.id = undefined
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
246
src/components/AppLinkInput/data.ts
Normal file
246
src/components/AppLinkInput/data.ts
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
// APP 链接类型(需要特殊处理,例如商品详情)
|
||||||
|
export const enum APP_LINK_TYPE_ENUM {
|
||||||
|
// 拼团活动
|
||||||
|
ACTIVITY_COMBINATION,
|
||||||
|
// 秒杀活动
|
||||||
|
ACTIVITY_SECKILL,
|
||||||
|
// 文章详情
|
||||||
|
ARTICLE_DETAIL,
|
||||||
|
// 优惠券详情
|
||||||
|
COUPON_DETAIL,
|
||||||
|
// 自定义页面详情
|
||||||
|
DIY_PAGE_DETAIL,
|
||||||
|
// 品类列表
|
||||||
|
PRODUCT_CATEGORY_LIST,
|
||||||
|
// 商品列表
|
||||||
|
PRODUCT_LIST,
|
||||||
|
// 商品详情
|
||||||
|
PRODUCT_DETAIL_NORMAL,
|
||||||
|
// 拼团商品详情
|
||||||
|
PRODUCT_DETAIL_COMBINATION,
|
||||||
|
// 积分商品详情
|
||||||
|
PRODUCT_DETAIL_POINT,
|
||||||
|
// 秒杀商品详情
|
||||||
|
PRODUCT_DETAIL_SECKILL
|
||||||
|
}
|
||||||
|
|
||||||
|
// APP 链接列表(做一下持久化?)
|
||||||
|
export const APP_LINK_GROUP_LIST = [
|
||||||
|
{
|
||||||
|
name: '商城',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
name: '首页',
|
||||||
|
path: '/pages/index/index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '商品分类',
|
||||||
|
path: '/pages/index/category',
|
||||||
|
type: APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '购物车',
|
||||||
|
path: '/pages/index/cart'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '个人中心',
|
||||||
|
path: '/pages/index/user'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '商品搜索',
|
||||||
|
path: '/pages/index/search'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '自定义页面',
|
||||||
|
path: '/pages/index/page',
|
||||||
|
type: APP_LINK_TYPE_ENUM.DIY_PAGE_DETAIL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '客服',
|
||||||
|
path: '/pages/chat/index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '系统设置',
|
||||||
|
path: '/pages/public/setting'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '问题反馈',
|
||||||
|
path: '/pages/public/feedback'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '常见问题',
|
||||||
|
path: '/pages/public/faq'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '商品',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
name: '商品列表',
|
||||||
|
path: '/pages/goods/list',
|
||||||
|
type: APP_LINK_TYPE_ENUM.PRODUCT_LIST
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '商品详情',
|
||||||
|
path: '/pages/goods/index',
|
||||||
|
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_NORMAL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '拼团商品详情',
|
||||||
|
path: '/pages/goods/groupon',
|
||||||
|
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_COMBINATION
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '秒杀商品详情',
|
||||||
|
path: '/pages/goods/seckill',
|
||||||
|
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_SECKILL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '积分商品详情',
|
||||||
|
path: '/pages/goods/score',
|
||||||
|
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_POINT
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '营销活动',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
name: '拼团订单',
|
||||||
|
path: '/pages/activity/groupon/order'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '营销商品',
|
||||||
|
path: '/pages/activity/index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '拼团活动',
|
||||||
|
path: '/pages/activity/groupon/list',
|
||||||
|
type: APP_LINK_TYPE_ENUM.ACTIVITY_COMBINATION
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '秒杀活动',
|
||||||
|
path: '/pages/activity/seckill/list',
|
||||||
|
type: APP_LINK_TYPE_ENUM.ACTIVITY_SECKILL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '签到中心',
|
||||||
|
path: '/pages/app/sign'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '积分商城',
|
||||||
|
path: '/pages/app/score-shop'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '优惠券中心',
|
||||||
|
path: '/pages/coupon/list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '优惠券详情',
|
||||||
|
path: '/pages/coupon/detail',
|
||||||
|
type: APP_LINK_TYPE_ENUM.COUPON_DETAIL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '文章详情',
|
||||||
|
path: '/pages/public/richtext',
|
||||||
|
type: APP_LINK_TYPE_ENUM.ARTICLE_DETAIL
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '分销商城',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
name: '分销中心',
|
||||||
|
path: '/pages/commission/index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '申请分销商',
|
||||||
|
path: '/pages/commission/apply'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '推广商品',
|
||||||
|
path: '/pages/commission/goods'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '分销订单',
|
||||||
|
path: '/pages/commission/order'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '分享记录',
|
||||||
|
path: '/pages/commission/share-log'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '我的团队',
|
||||||
|
path: '/pages/commission/team'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '支付',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
name: '充值余额',
|
||||||
|
path: '/pages/pay/recharge'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '充值记录',
|
||||||
|
path: '/pages/pay/recharge-log'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '申请提现',
|
||||||
|
path: '/pages/pay/withdraw'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '提现记录',
|
||||||
|
path: '/pages/pay/withdraw-log'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '用户中心',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
name: '用户信息',
|
||||||
|
path: '/pages/user/info'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '用户订单',
|
||||||
|
path: '/pages/order/list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '售后订单',
|
||||||
|
path: '/pages/order/aftersale/list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '商品收藏',
|
||||||
|
path: '/pages/user/goods-collect'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '浏览记录',
|
||||||
|
path: '/pages/user/goods-log'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '地址管理',
|
||||||
|
path: '/pages/user/address/list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '发票管理',
|
||||||
|
path: '/pages/user/invoice/list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '用户佣金',
|
||||||
|
path: '/pages/user/wallet/commission'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '用户余额',
|
||||||
|
path: '/pages/user/wallet/money'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '用户积分',
|
||||||
|
path: '/pages/user/wallet/score'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
43
src/components/AppLinkInput/index.vue
Normal file
43
src/components/AppLinkInput/index.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<el-input v-model="appLink" placeholder="输入或选择链接">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="handleOpenDialog">选择</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<AppLinkSelectDialog ref="dialogRef" @change="handleLinkSelected" />
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
|
||||||
|
// APP 链接输入框
|
||||||
|
defineOptions({ name: 'AppLinkInput' })
|
||||||
|
// 定义属性
|
||||||
|
const props = defineProps({
|
||||||
|
// 当前选中的链接
|
||||||
|
modelValue: propTypes.string.def('')
|
||||||
|
})
|
||||||
|
// 当前的链接
|
||||||
|
const appLink = ref('')
|
||||||
|
// 选择对话框
|
||||||
|
const dialogRef = ref()
|
||||||
|
// 处理打开对话框
|
||||||
|
const handleOpenDialog = () => dialogRef.value?.open(appLink.value)
|
||||||
|
// 处理 APP 链接选中
|
||||||
|
const handleLinkSelected = (link: string) => (appLink.value = link)
|
||||||
|
|
||||||
|
// getter
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
() => (appLink.value = props.modelValue),
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// setter
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [link: string]
|
||||||
|
}>()
|
||||||
|
watch(
|
||||||
|
() => appLink,
|
||||||
|
() => emit('update:modelValue', appLink.value)
|
||||||
|
)
|
||||||
|
</script>
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-tabs stretch>
|
<el-tabs stretch>
|
||||||
<el-tab-pane label="内容">
|
<el-tab-pane label="内容" v-if="$slots.default">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="样式" lazy>
|
<el-tab-pane label="样式" lazy>
|
||||||
|
@ -103,7 +103,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
<el-form-item label="链接" class="m-b-8px!" label-width="50px">
|
<el-form-item label="链接" class="m-b-8px!" label-width="50px">
|
||||||
<el-input placeholder="链接" v-model="element.url" />
|
<AppLinkInput v-model="element.url" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
</UploadImg>
|
</UploadImg>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="链接" prop="url">
|
<el-form-item label="链接" prop="url">
|
||||||
<el-input placeholder="链接" v-model="formData.url" />
|
<AppLinkInput v-model="formData.url" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<UploadImg v-model="hotArea.imgUrl" height="80px" width="80px" />
|
<UploadImg v-model="hotArea.imgUrl" height="80px" width="80px" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="链接" :prop="`list[${index}].url`">
|
<el-form-item label="链接" :prop="`list[${index}].url`">
|
||||||
<el-input v-model="hotArea.url" placeholder="请输入链接" />
|
<AppLinkInput v-model="hotArea.url" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
|
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="链接" prop="url">
|
<el-form-item label="链接" prop="url">
|
||||||
<el-input v-model="element.url" />
|
<AppLinkInput v-model="element.url" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="显示角标" prop="badge.show">
|
<el-form-item label="显示角标" prop="badge.show">
|
||||||
<el-switch v-model="element.badge.show" />
|
<el-switch v-model="element.badge.show" />
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
|
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="链接" prop="url">
|
<el-form-item label="链接" prop="url">
|
||||||
<el-input v-model="element.url" />
|
<AppLinkInput v-model="element.url" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
|
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="链接" prop="url">
|
<el-form-item label="链接" prop="url">
|
||||||
<el-input v-model="element.url" />
|
<AppLinkInput v-model="element.url" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="显示角标" prop="badge.show">
|
<el-form-item label="显示角标" prop="badge.show">
|
||||||
<el-switch v-model="element.badge.show" />
|
<el-switch v-model="element.badge.show" />
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="w-full flex flex-col gap-8px">
|
<div class="w-full flex flex-col gap-8px">
|
||||||
<el-input v-model="element.text" placeholder="请输入公告" />
|
<el-input v-model="element.text" placeholder="请输入公告" />
|
||||||
<el-input v-model="element.url" placeholder="请输入链接" />
|
<AppLinkInput v-model="element.url" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
/** 商品卡片属性 */
|
/** 商品栏属性 */
|
||||||
export interface ProductListProperty {
|
export interface ProductListProperty {
|
||||||
// 布局类型:双列 | 三列 | 水平滑动
|
// 布局类型:双列 | 三列 | 水平滑动
|
||||||
layoutType: 'twoCol' | 'threeCol' | 'horizSwiper'
|
layoutType: 'twoCol' | 'threeCol' | 'horizSwiper'
|
||||||
|
@ -66,7 +66,7 @@
|
|||||||
import { ProductListProperty } from './config'
|
import { ProductListProperty } from './config'
|
||||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||||
|
|
||||||
/** 商品卡片 */
|
/** 商品栏 */
|
||||||
defineOptions({ name: 'ProductList' })
|
defineOptions({ name: 'ProductList' })
|
||||||
// 定义属性
|
// 定义属性
|
||||||
const props = defineProps<{ property: ProductListProperty }>()
|
const props = defineProps<{ property: ProductListProperty }>()
|
||||||
|
@ -88,7 +88,7 @@ import { ProductListProperty } from './config'
|
|||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
|
import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
|
||||||
|
|
||||||
// 商品卡片属性面板
|
// 商品栏属性面板
|
||||||
defineOptions({ name: 'ProductListProperty' })
|
defineOptions({ name: 'ProductListProperty' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: ProductListProperty }>()
|
const props = defineProps<{ modelValue: ProductListProperty }>()
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
/** 营销文章属性 */
|
||||||
|
export interface PromotionArticleProperty {
|
||||||
|
// 文章编号
|
||||||
|
id: number
|
||||||
|
// 组件样式
|
||||||
|
style: ComponentStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件
|
||||||
|
export const component = {
|
||||||
|
id: 'PromotionArticle',
|
||||||
|
name: '营销文章',
|
||||||
|
icon: 'ph:article-medium',
|
||||||
|
property: {
|
||||||
|
style: {
|
||||||
|
bgType: 'color',
|
||||||
|
bgColor: '',
|
||||||
|
marginLeft: 8,
|
||||||
|
marginRight: 8,
|
||||||
|
marginBottom: 8
|
||||||
|
} as ComponentStyle
|
||||||
|
}
|
||||||
|
} as DiyComponent<PromotionArticleProperty>
|
@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<div class="min-h-30px" v-html="article.content"></div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { PromotionArticleProperty } from './config'
|
||||||
|
import * as ArticleApi from '@/api/mall/promotion/article/index'
|
||||||
|
|
||||||
|
/** 营销文章 */
|
||||||
|
defineOptions({ name: 'PromotionArticle' })
|
||||||
|
// 定义属性
|
||||||
|
const props = defineProps<{ property: PromotionArticleProperty }>()
|
||||||
|
// 商品列表
|
||||||
|
const article = ref<ArticleApi.ArticleVO[]>({})
|
||||||
|
watch(
|
||||||
|
() => props.property.id,
|
||||||
|
async () => {
|
||||||
|
if (props.property.id) {
|
||||||
|
article.value = await ArticleApi.getArticle(props.property.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<ComponentContainerProperty v-model="formData.style">
|
||||||
|
<el-form label-width="40px" :model="formData">
|
||||||
|
<el-form-item label="文章" prop="id">
|
||||||
|
<el-select
|
||||||
|
v-model="formData.id"
|
||||||
|
placeholder="请选择文章"
|
||||||
|
class="w-full"
|
||||||
|
filterable
|
||||||
|
remote
|
||||||
|
:remote-method="queryArticleList"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="article in articles"
|
||||||
|
:key="article.id"
|
||||||
|
:label="article.title"
|
||||||
|
:value="article.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ComponentContainerProperty>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { PromotionArticleProperty } from './config'
|
||||||
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
|
import * as ArticleApi from '@/api/mall/promotion/article/index'
|
||||||
|
|
||||||
|
// 营销文章属性面板
|
||||||
|
defineOptions({ name: 'PromotionArticleProperty' })
|
||||||
|
|
||||||
|
const props = defineProps<{ modelValue: PromotionArticleProperty }>()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
// 文章列表
|
||||||
|
const articles = ref<ArticleApi.ArticleVO>([])
|
||||||
|
|
||||||
|
// 加载中
|
||||||
|
const loading = ref(false)
|
||||||
|
// 查询文章列表
|
||||||
|
const queryArticleList = async (title?: string) => {
|
||||||
|
loading.value = true
|
||||||
|
const { list } = await ArticleApi.getArticlePage({ title, pageSize: 10 })
|
||||||
|
articles.value = list
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
queryArticleList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,64 @@
|
|||||||
|
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
/** 拼团属性 */
|
||||||
|
export interface PromotionCombinationProperty {
|
||||||
|
// 布局类型:单列 | 三列
|
||||||
|
layoutType: 'oneCol' | 'threeCol'
|
||||||
|
// 商品字段
|
||||||
|
fields: {
|
||||||
|
// 商品名称
|
||||||
|
name: PromotionCombinationFieldProperty
|
||||||
|
// 商品价格
|
||||||
|
price: PromotionCombinationFieldProperty
|
||||||
|
}
|
||||||
|
// 角标
|
||||||
|
badge: {
|
||||||
|
// 是否显示
|
||||||
|
show: boolean
|
||||||
|
// 角标图片
|
||||||
|
imgUrl: string
|
||||||
|
}
|
||||||
|
// 上圆角
|
||||||
|
borderRadiusTop: number
|
||||||
|
// 下圆角
|
||||||
|
borderRadiusBottom: number
|
||||||
|
// 间距
|
||||||
|
space: number
|
||||||
|
// 拼团活动编号
|
||||||
|
activityId: number
|
||||||
|
// 组件样式
|
||||||
|
style: ComponentStyle
|
||||||
|
}
|
||||||
|
// 商品字段
|
||||||
|
export interface PromotionCombinationFieldProperty {
|
||||||
|
// 是否显示
|
||||||
|
show: boolean
|
||||||
|
// 颜色
|
||||||
|
color: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件
|
||||||
|
export const component = {
|
||||||
|
id: 'PromotionCombination',
|
||||||
|
name: '拼团',
|
||||||
|
icon: 'mdi:account-group',
|
||||||
|
property: {
|
||||||
|
activityId: undefined,
|
||||||
|
layoutType: 'oneCol',
|
||||||
|
fields: {
|
||||||
|
name: { show: true, color: '#000' },
|
||||||
|
price: { show: true, color: '#ff3000' }
|
||||||
|
},
|
||||||
|
badge: { show: false, imgUrl: '' },
|
||||||
|
borderRadiusTop: 8,
|
||||||
|
borderRadiusBottom: 8,
|
||||||
|
space: 8,
|
||||||
|
style: {
|
||||||
|
bgType: 'color',
|
||||||
|
bgColor: '',
|
||||||
|
marginLeft: 8,
|
||||||
|
marginRight: 8,
|
||||||
|
marginBottom: 8
|
||||||
|
} as ComponentStyle
|
||||||
|
}
|
||||||
|
} as DiyComponent<PromotionCombinationProperty>
|
@ -0,0 +1,125 @@
|
|||||||
|
<template>
|
||||||
|
<el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
|
||||||
|
<!-- 商品网格 -->
|
||||||
|
<div
|
||||||
|
class="grid overflow-x-auto"
|
||||||
|
:style="{
|
||||||
|
gridGap: `${property.space}px`,
|
||||||
|
gridTemplateColumns,
|
||||||
|
width: scrollbarWidth
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<!-- 商品 -->
|
||||||
|
<div
|
||||||
|
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
|
||||||
|
:style="{
|
||||||
|
borderTopLeftRadius: `${property.borderRadiusTop}px`,
|
||||||
|
borderTopRightRadius: `${property.borderRadiusTop}px`,
|
||||||
|
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
|
||||||
|
borderBottomRightRadius: `${property.borderRadiusBottom}px`
|
||||||
|
}"
|
||||||
|
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>
|
||||||
|
<!-- 商品封面图 -->
|
||||||
|
<el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" />
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'flex flex-col gap-8px p-8px box-border',
|
||||||
|
{
|
||||||
|
'w-[calc(100%-64px)]': columns === 2,
|
||||||
|
'w-full': columns === 3
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<!-- 商品名称 -->
|
||||||
|
<div
|
||||||
|
v-if="property.fields.name.show"
|
||||||
|
class="truncate text-12px"
|
||||||
|
:style="{ color: property.fields.name.color }"
|
||||||
|
>
|
||||||
|
{{ spu.name }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<!-- 商品价格 -->
|
||||||
|
<span
|
||||||
|
v-if="property.fields.price.show"
|
||||||
|
class="text-12px"
|
||||||
|
:style="{ color: property.fields.price.color }"
|
||||||
|
>
|
||||||
|
¥{{ spu.price }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { PromotionCombinationProperty } from './config'
|
||||||
|
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||||
|
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
|
||||||
|
|
||||||
|
/** 拼团 */
|
||||||
|
defineOptions({ name: 'PromotionCombination' })
|
||||||
|
// 定义属性
|
||||||
|
const props = defineProps<{ property: PromotionCombinationProperty }>()
|
||||||
|
// 商品列表
|
||||||
|
const spuList = ref<ProductSpuApi.Spu[]>([])
|
||||||
|
watch(
|
||||||
|
() => props.property.activityId,
|
||||||
|
async () => {
|
||||||
|
if (!props.property.activityId) return
|
||||||
|
const activity = await CombinationActivityApi.getCombinationActivity(props.property.activityId)
|
||||||
|
if (!activity?.spuId) return
|
||||||
|
spuList.value = [await ProductSpuApi.getSpu(activity.spuId)]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// 手机宽度
|
||||||
|
const phoneWidth = ref(375)
|
||||||
|
// 容器
|
||||||
|
const containerRef = ref()
|
||||||
|
// 商品的列数
|
||||||
|
const columns = ref(2)
|
||||||
|
// 滚动条宽度
|
||||||
|
const scrollbarWidth = ref('100%')
|
||||||
|
// 商品图大小
|
||||||
|
const imageSize = ref('0')
|
||||||
|
// 商品网络列数
|
||||||
|
const gridTemplateColumns = ref('')
|
||||||
|
// 计算布局参数
|
||||||
|
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>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,112 @@
|
|||||||
|
<template>
|
||||||
|
<ComponentContainerProperty v-model="formData.style">
|
||||||
|
<el-form label-width="80px" :model="formData">
|
||||||
|
<el-card header="拼团活动" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="拼团活动" prop="activityId">
|
||||||
|
<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 header="商品样式" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="布局" prop="type">
|
||||||
|
<el-radio-group v-model="formData.layoutType">
|
||||||
|
<el-tooltip class="item" content="单列" placement="bottom">
|
||||||
|
<el-radio-button label="oneCol">
|
||||||
|
<Icon icon="fluent:text-column-one-24-filled" />
|
||||||
|
</el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip class="item" content="三列" placement="bottom">
|
||||||
|
<el-radio-button label="threeCol">
|
||||||
|
<Icon icon="fluent:text-column-three-24-filled" />
|
||||||
|
</el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品名称" prop="fields.name.show">
|
||||||
|
<div class="flex gap-8px">
|
||||||
|
<ColorInput v-model="formData.fields.name.color" />
|
||||||
|
<el-checkbox v-model="formData.fields.name.show" />
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品价格" prop="fields.price.show">
|
||||||
|
<div class="flex gap-8px">
|
||||||
|
<ColorInput v-model="formData.fields.price.color" />
|
||||||
|
<el-checkbox v-model="formData.fields.price.show" />
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-card>
|
||||||
|
<el-card header="角标" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="角标" prop="badge.show">
|
||||||
|
<el-switch v-model="formData.badge.show" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
|
||||||
|
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
|
||||||
|
<template #tip> 建议尺寸:36 * 22 </template>
|
||||||
|
</UploadImg>
|
||||||
|
</el-form-item>
|
||||||
|
</el-card>
|
||||||
|
<el-card header="商品样式" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="上圆角" prop="borderRadiusTop">
|
||||||
|
<el-slider
|
||||||
|
v-model="formData.borderRadiusTop"
|
||||||
|
:max="100"
|
||||||
|
:min="0"
|
||||||
|
show-input
|
||||||
|
input-size="small"
|
||||||
|
:show-input-controls="false"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="下圆角" prop="borderRadiusBottom">
|
||||||
|
<el-slider
|
||||||
|
v-model="formData.borderRadiusBottom"
|
||||||
|
:max="100"
|
||||||
|
:min="0"
|
||||||
|
show-input
|
||||||
|
input-size="small"
|
||||||
|
:show-input-controls="false"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="间隔" prop="space">
|
||||||
|
<el-slider
|
||||||
|
v-model="formData.space"
|
||||||
|
:max="100"
|
||||||
|
:min="0"
|
||||||
|
show-input
|
||||||
|
input-size="small"
|
||||||
|
:show-input-controls="false"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-card>
|
||||||
|
</el-form>
|
||||||
|
</ComponentContainerProperty>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { PromotionCombinationProperty } from './config'
|
||||||
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
|
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
|
||||||
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
|
||||||
|
// 拼团属性面板
|
||||||
|
defineOptions({ name: 'PromotionCombinationProperty' })
|
||||||
|
|
||||||
|
const props = defineProps<{ modelValue: PromotionCombinationProperty }>()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
// 活动列表
|
||||||
|
const activityList = ref<CombinationActivityApi.CombinationActivityVO>([])
|
||||||
|
onMounted(async () => {
|
||||||
|
const { list } = await CombinationActivityApi.getCombinationActivityPage({
|
||||||
|
status: CommonStatusEnum.ENABLE
|
||||||
|
})
|
||||||
|
activityList.value = list
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,64 @@
|
|||||||
|
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
/** 秒杀属性 */
|
||||||
|
export interface PromotionSeckillProperty {
|
||||||
|
// 布局类型:单列 | 三列
|
||||||
|
layoutType: 'oneCol' | 'threeCol'
|
||||||
|
// 商品字段
|
||||||
|
fields: {
|
||||||
|
// 商品名称
|
||||||
|
name: PromotionSeckillFieldProperty
|
||||||
|
// 商品价格
|
||||||
|
price: PromotionSeckillFieldProperty
|
||||||
|
}
|
||||||
|
// 角标
|
||||||
|
badge: {
|
||||||
|
// 是否显示
|
||||||
|
show: boolean
|
||||||
|
// 角标图片
|
||||||
|
imgUrl: string
|
||||||
|
}
|
||||||
|
// 上圆角
|
||||||
|
borderRadiusTop: number
|
||||||
|
// 下圆角
|
||||||
|
borderRadiusBottom: number
|
||||||
|
// 间距
|
||||||
|
space: number
|
||||||
|
// 秒杀活动编号
|
||||||
|
activityId: number
|
||||||
|
// 组件样式
|
||||||
|
style: ComponentStyle
|
||||||
|
}
|
||||||
|
// 商品字段
|
||||||
|
export interface PromotionSeckillFieldProperty {
|
||||||
|
// 是否显示
|
||||||
|
show: boolean
|
||||||
|
// 颜色
|
||||||
|
color: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件
|
||||||
|
export const component = {
|
||||||
|
id: 'PromotionSeckill',
|
||||||
|
name: '秒杀',
|
||||||
|
icon: 'mdi:calendar-time',
|
||||||
|
property: {
|
||||||
|
activityId: undefined,
|
||||||
|
layoutType: 'oneCol',
|
||||||
|
fields: {
|
||||||
|
name: { show: true, color: '#000' },
|
||||||
|
price: { show: true, color: '#ff3000' }
|
||||||
|
},
|
||||||
|
badge: { show: false, imgUrl: '' },
|
||||||
|
borderRadiusTop: 8,
|
||||||
|
borderRadiusBottom: 8,
|
||||||
|
space: 8,
|
||||||
|
style: {
|
||||||
|
bgType: 'color',
|
||||||
|
bgColor: '',
|
||||||
|
marginLeft: 8,
|
||||||
|
marginRight: 8,
|
||||||
|
marginBottom: 8
|
||||||
|
} as ComponentStyle
|
||||||
|
}
|
||||||
|
} as DiyComponent<PromotionSeckillProperty>
|
@ -0,0 +1,125 @@
|
|||||||
|
<template>
|
||||||
|
<el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
|
||||||
|
<!-- 商品网格 -->
|
||||||
|
<div
|
||||||
|
class="grid overflow-x-auto"
|
||||||
|
:style="{
|
||||||
|
gridGap: `${property.space}px`,
|
||||||
|
gridTemplateColumns,
|
||||||
|
width: scrollbarWidth
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<!-- 商品 -->
|
||||||
|
<div
|
||||||
|
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
|
||||||
|
:style="{
|
||||||
|
borderTopLeftRadius: `${property.borderRadiusTop}px`,
|
||||||
|
borderTopRightRadius: `${property.borderRadiusTop}px`,
|
||||||
|
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
|
||||||
|
borderBottomRightRadius: `${property.borderRadiusBottom}px`
|
||||||
|
}"
|
||||||
|
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>
|
||||||
|
<!-- 商品封面图 -->
|
||||||
|
<el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" />
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'flex flex-col gap-8px p-8px box-border',
|
||||||
|
{
|
||||||
|
'w-[calc(100%-64px)]': columns === 2,
|
||||||
|
'w-full': columns === 3
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<!-- 商品名称 -->
|
||||||
|
<div
|
||||||
|
v-if="property.fields.name.show"
|
||||||
|
class="truncate text-12px"
|
||||||
|
:style="{ color: property.fields.name.color }"
|
||||||
|
>
|
||||||
|
{{ spu.name }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<!-- 商品价格 -->
|
||||||
|
<span
|
||||||
|
v-if="property.fields.price.show"
|
||||||
|
class="text-12px"
|
||||||
|
:style="{ color: property.fields.price.color }"
|
||||||
|
>
|
||||||
|
¥{{ spu.price }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { PromotionSeckillProperty } from './config'
|
||||||
|
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||||
|
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
|
||||||
|
|
||||||
|
/** 秒杀 */
|
||||||
|
defineOptions({ name: 'PromotionSeckill' })
|
||||||
|
// 定义属性
|
||||||
|
const props = defineProps<{ property: PromotionSeckillProperty }>()
|
||||||
|
// 商品列表
|
||||||
|
const spuList = ref<ProductSpuApi.Spu[]>([])
|
||||||
|
watch(
|
||||||
|
() => props.property.activityId,
|
||||||
|
async () => {
|
||||||
|
if (!props.property.activityId) return
|
||||||
|
const activity = await SeckillActivityApi.getSeckillActivity(props.property.activityId)
|
||||||
|
if (!activity?.spuId) return
|
||||||
|
spuList.value = [await ProductSpuApi.getSpu(activity.spuId)]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// 手机宽度
|
||||||
|
const phoneWidth = ref(375)
|
||||||
|
// 容器
|
||||||
|
const containerRef = ref()
|
||||||
|
// 商品的列数
|
||||||
|
const columns = ref(2)
|
||||||
|
// 滚动条宽度
|
||||||
|
const scrollbarWidth = ref('100%')
|
||||||
|
// 商品图大小
|
||||||
|
const imageSize = ref('0')
|
||||||
|
// 商品网络列数
|
||||||
|
const gridTemplateColumns = ref('')
|
||||||
|
// 计算布局参数
|
||||||
|
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>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,112 @@
|
|||||||
|
<template>
|
||||||
|
<ComponentContainerProperty v-model="formData.style">
|
||||||
|
<el-form label-width="80px" :model="formData">
|
||||||
|
<el-card header="秒杀活动" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="秒杀活动" prop="activityId">
|
||||||
|
<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 header="商品样式" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="布局" prop="type">
|
||||||
|
<el-radio-group v-model="formData.layoutType">
|
||||||
|
<el-tooltip class="item" content="单列" placement="bottom">
|
||||||
|
<el-radio-button label="oneCol">
|
||||||
|
<Icon icon="fluent:text-column-one-24-filled" />
|
||||||
|
</el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip class="item" content="三列" placement="bottom">
|
||||||
|
<el-radio-button label="threeCol">
|
||||||
|
<Icon icon="fluent:text-column-three-24-filled" />
|
||||||
|
</el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品名称" prop="fields.name.show">
|
||||||
|
<div class="flex gap-8px">
|
||||||
|
<ColorInput v-model="formData.fields.name.color" />
|
||||||
|
<el-checkbox v-model="formData.fields.name.show" />
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品价格" prop="fields.price.show">
|
||||||
|
<div class="flex gap-8px">
|
||||||
|
<ColorInput v-model="formData.fields.price.color" />
|
||||||
|
<el-checkbox v-model="formData.fields.price.show" />
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-card>
|
||||||
|
<el-card header="角标" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="角标" prop="badge.show">
|
||||||
|
<el-switch v-model="formData.badge.show" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
|
||||||
|
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
|
||||||
|
<template #tip> 建议尺寸:36 * 22 </template>
|
||||||
|
</UploadImg>
|
||||||
|
</el-form-item>
|
||||||
|
</el-card>
|
||||||
|
<el-card header="商品样式" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="上圆角" prop="borderRadiusTop">
|
||||||
|
<el-slider
|
||||||
|
v-model="formData.borderRadiusTop"
|
||||||
|
:max="100"
|
||||||
|
:min="0"
|
||||||
|
show-input
|
||||||
|
input-size="small"
|
||||||
|
:show-input-controls="false"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="下圆角" prop="borderRadiusBottom">
|
||||||
|
<el-slider
|
||||||
|
v-model="formData.borderRadiusBottom"
|
||||||
|
:max="100"
|
||||||
|
:min="0"
|
||||||
|
show-input
|
||||||
|
input-size="small"
|
||||||
|
:show-input-controls="false"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="间隔" prop="space">
|
||||||
|
<el-slider
|
||||||
|
v-model="formData.space"
|
||||||
|
:max="100"
|
||||||
|
:min="0"
|
||||||
|
show-input
|
||||||
|
input-size="small"
|
||||||
|
:show-input-controls="false"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-card>
|
||||||
|
</el-form>
|
||||||
|
</ComponentContainerProperty>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { PromotionSeckillProperty } from './config'
|
||||||
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
|
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
|
||||||
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
|
||||||
|
// 秒杀属性面板
|
||||||
|
defineOptions({ name: 'PromotionSeckillProperty' })
|
||||||
|
|
||||||
|
const props = defineProps<{ modelValue: PromotionSeckillProperty }>()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
// 活动列表
|
||||||
|
const activityList = ref<SeckillActivityApi.SeckillActivityVO>([])
|
||||||
|
onMounted(async () => {
|
||||||
|
const { list } = await SeckillActivityApi.getSeckillActivityPage({
|
||||||
|
status: CommonStatusEnum.ENABLE
|
||||||
|
})
|
||||||
|
activityList.value = list
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -88,7 +88,7 @@
|
|||||||
<el-input v-model="element.text" placeholder="请输入文字" />
|
<el-input v-model="element.text" placeholder="请输入文字" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="url" label-width="0" class="m-b-0!">
|
<el-form-item prop="url" label-width="0" class="m-b-0!">
|
||||||
<el-input v-model="element.url" placeholder="请选择链接" />
|
<AppLinkInput v-model="element.url" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -92,7 +92,7 @@
|
|||||||
<el-input v-model="formData.more.text" />
|
<el-input v-model="formData.more.text" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="跳转链接" prop="more.url">
|
<el-form-item label="跳转链接" prop="more.url">
|
||||||
<el-input v-model="formData.more.url" placeholder="请输入跳转链接" />
|
<AppLinkInput v-model="formData.more.url" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
/** 用户卡片属性 */
|
||||||
|
export interface UserCardProperty {
|
||||||
|
// 组件样式
|
||||||
|
style: ComponentStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件
|
||||||
|
export const component = {
|
||||||
|
id: 'UserCard',
|
||||||
|
name: '用户卡片',
|
||||||
|
icon: 'mdi:user-card-details',
|
||||||
|
property: {
|
||||||
|
style: {
|
||||||
|
bgType: 'color',
|
||||||
|
bgColor: '',
|
||||||
|
marginBottom: 8
|
||||||
|
} as ComponentStyle
|
||||||
|
}
|
||||||
|
} as DiyComponent<UserCardProperty>
|
@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex items-center justify-between p-x-18px p-y-24px">
|
||||||
|
<div class="flex flex-1 items-center gap-16px">
|
||||||
|
<el-avatar :size="60">
|
||||||
|
<Icon icon="ep:avatar" :size="60" />
|
||||||
|
</el-avatar>
|
||||||
|
<span class="text-18px font-bold">芋道源码</span>
|
||||||
|
</div>
|
||||||
|
<Icon icon="tdesign:qrcode" :size="20" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between justify-between bg-white p-x-20px p-y-8px text-12px"
|
||||||
|
>
|
||||||
|
<span class="color-#ff690d">点击绑定手机号</span>
|
||||||
|
<span class="rounded-26px bg-#ff6100 p-x-8px p-y-5px color-white">去绑定</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { UserCardProperty } from './config'
|
||||||
|
|
||||||
|
/** 用户卡片 */
|
||||||
|
defineOptions({ name: 'UserCard' })
|
||||||
|
// 定义属性
|
||||||
|
defineProps<{ property: UserCardProperty }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<ComponentContainerProperty v-model="formData.style" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { UserCardProperty } from './config'
|
||||||
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
// 用户卡片属性面板
|
||||||
|
defineOptions({ name: 'UserCardProperty' })
|
||||||
|
|
||||||
|
const props = defineProps<{ modelValue: UserCardProperty }>()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
/** 用户卡券属性 */
|
||||||
|
export interface UserCouponProperty {
|
||||||
|
// 组件样式
|
||||||
|
style: ComponentStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件
|
||||||
|
export const component = {
|
||||||
|
id: 'UserCoupon',
|
||||||
|
name: '用户卡券',
|
||||||
|
icon: 'ep:ticket',
|
||||||
|
property: {
|
||||||
|
style: {
|
||||||
|
bgType: 'color',
|
||||||
|
bgColor: '',
|
||||||
|
marginLeft: 8,
|
||||||
|
marginRight: 8,
|
||||||
|
marginBottom: 8
|
||||||
|
} as ComponentStyle
|
||||||
|
}
|
||||||
|
} as DiyComponent<UserCouponProperty>
|
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<el-image
|
||||||
|
src="https://shopro.sheepjs.com/admin/static/images/shop/decorate/couponCardStyle.png"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { UserCouponProperty } from './config'
|
||||||
|
|
||||||
|
/** 用户卡券 */
|
||||||
|
defineOptions({ name: 'UserCoupon' })
|
||||||
|
// 定义属性
|
||||||
|
defineProps<{ property: UserCouponProperty }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<ComponentContainerProperty v-model="formData.style" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { UserCouponProperty } from './config'
|
||||||
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
// 用户卡券属性面板
|
||||||
|
defineOptions({ name: 'UserCouponProperty' })
|
||||||
|
|
||||||
|
const props = defineProps<{ modelValue: UserCouponProperty }>()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
/** 用户订单属性 */
|
||||||
|
export interface UserOrderProperty {
|
||||||
|
// 组件样式
|
||||||
|
style: ComponentStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件
|
||||||
|
export const component = {
|
||||||
|
id: 'UserOrder',
|
||||||
|
name: '用户订单',
|
||||||
|
icon: 'ep:list',
|
||||||
|
property: {
|
||||||
|
style: {
|
||||||
|
bgType: 'color',
|
||||||
|
bgColor: '',
|
||||||
|
marginLeft: 8,
|
||||||
|
marginRight: 8,
|
||||||
|
marginBottom: 8
|
||||||
|
} as ComponentStyle
|
||||||
|
}
|
||||||
|
} as DiyComponent<UserOrderProperty>
|
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<el-image src="https://shopro.sheepjs.com/admin/static/images/shop/decorate/orderCardStyle.png" />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { UserOrderProperty } from './config'
|
||||||
|
|
||||||
|
/** 用户订单 */
|
||||||
|
defineOptions({ name: 'UserOrder' })
|
||||||
|
// 定义属性
|
||||||
|
defineProps<{ property: UserOrderProperty }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<ComponentContainerProperty v-model="formData.style" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { UserOrderProperty } from './config'
|
||||||
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
// 用户订单属性面板
|
||||||
|
defineOptions({ name: 'UserOrderProperty' })
|
||||||
|
|
||||||
|
const props = defineProps<{ modelValue: UserOrderProperty }>()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
/** 用户资产属性 */
|
||||||
|
export interface UserWalletProperty {
|
||||||
|
// 组件样式
|
||||||
|
style: ComponentStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件
|
||||||
|
export const component = {
|
||||||
|
id: 'UserWallet',
|
||||||
|
name: '用户资产',
|
||||||
|
icon: 'ep:wallet-filled',
|
||||||
|
property: {
|
||||||
|
style: {
|
||||||
|
bgType: 'color',
|
||||||
|
bgColor: '',
|
||||||
|
marginLeft: 8,
|
||||||
|
marginRight: 8,
|
||||||
|
marginBottom: 8
|
||||||
|
} as ComponentStyle
|
||||||
|
}
|
||||||
|
} as DiyComponent<UserWalletProperty>
|
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<el-image
|
||||||
|
src="https://shopro.sheepjs.com/admin/static/images/shop/decorate/walletCardStyle.png"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { UserWalletProperty } from './config'
|
||||||
|
|
||||||
|
/** 用户资产 */
|
||||||
|
defineOptions({ name: 'UserWallet' })
|
||||||
|
// 定义属性
|
||||||
|
defineProps<{ property: UserWalletProperty }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<ComponentContainerProperty v-model="formData.style" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { UserWalletProperty } from './config'
|
||||||
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
// 用户资产属性面板
|
||||||
|
defineOptions({ name: 'UserWalletProperty' })
|
||||||
|
|
||||||
|
const props = defineProps<{ modelValue: UserWalletProperty }>()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -109,13 +109,19 @@ export const PAGE_LIBS = [
|
|||||||
},
|
},
|
||||||
{ name: '商品组件', extended: true, components: ['ProductCard', 'ProductList'] },
|
{ name: '商品组件', extended: true, components: ['ProductCard', 'ProductList'] },
|
||||||
{
|
{
|
||||||
name: '会员组件',
|
name: '用户组件',
|
||||||
extended: true,
|
extended: true,
|
||||||
components: ['UserCard', 'UserOrder', 'UserWallet', 'UserCoupon']
|
components: ['UserCard', 'UserOrder', 'UserWallet', 'UserCoupon']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '营销组件',
|
name: '营销组件',
|
||||||
extended: true,
|
extended: true,
|
||||||
components: ['CombinationCard', 'SeckillCard', 'PointCard', 'CouponCard']
|
components: [
|
||||||
|
'PromotionCombination',
|
||||||
|
'PromotionSeckill',
|
||||||
|
'PromotionPoint',
|
||||||
|
'CouponCard',
|
||||||
|
'PromotionArticle'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
] as DiyComponentLibrary[]
|
] as DiyComponentLibrary[]
|
||||||
|
@ -29,7 +29,12 @@
|
|||||||
:class="showTopSearch ? 'w-220px ml2' : 'w-0'"
|
:class="showTopSearch ? 'w-220px ml2' : 'w-0'"
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
>
|
>
|
||||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
<el-option
|
||||||
|
v-for="item in options"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -73,7 +78,7 @@ function remoteMethod(data) {
|
|||||||
|
|
||||||
function handleChange(path) {
|
function handleChange(path) {
|
||||||
router.push({ path })
|
router.push({ path })
|
||||||
hiddenTopSearch();
|
hiddenTopSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
function hiddenTopSearch() {
|
function hiddenTopSearch() {
|
||||||
|
@ -65,7 +65,7 @@ export default defineComponent({
|
|||||||
{screenfull.value ? (
|
{screenfull.value ? (
|
||||||
<Screenfull class="custom-hover" color="var(--top-header-text-color)"></Screenfull>
|
<Screenfull class="custom-hover" color="var(--top-header-text-color)"></Screenfull>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
{search.value ? (<RouterSearch isModal={false} />) : undefined}
|
{search.value ? <RouterSearch isModal={false} /> : undefined}
|
||||||
{size.value ? (
|
{size.value ? (
|
||||||
<SizeDropdown class="custom-hover" color="var(--top-header-text-color)"></SizeDropdown>
|
<SizeDropdown class="custom-hover" color="var(--top-header-text-color)"></SizeDropdown>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { toNumber } from 'lodash-es'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param component 需要注册的组件
|
* @param component 需要注册的组件
|
||||||
@ -263,3 +265,23 @@ export const calculateRelativeRate = (value?: number, reference?: number) => {
|
|||||||
|
|
||||||
return ((100 * ((value || 0) - reference)) / reference).toFixed(0)
|
return ((100 * ((value || 0) - reference)) / reference).toFixed(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取链接的参数值
|
||||||
|
* @param key 参数键名
|
||||||
|
* @param urlStr 链接地址,默认为当前浏览器的地址
|
||||||
|
*/
|
||||||
|
export const getUrlValue = (key: string, urlStr: string = location.href): string => {
|
||||||
|
if (!urlStr || !key) return ''
|
||||||
|
const url = new URL(decodeURIComponent(urlStr))
|
||||||
|
return url.searchParams.get(key) ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取链接的参数值(值类型)
|
||||||
|
* @param key 参数键名
|
||||||
|
* @param urlStr 链接地址,默认为当前浏览器的地址
|
||||||
|
*/
|
||||||
|
export const getUrlNumberValue = (key: string, urlStr: string = location.href): number => {
|
||||||
|
return toNumber(getUrlValue(key, urlStr))
|
||||||
|
}
|
||||||
|
@ -95,7 +95,7 @@ const list = ref([]) // 列表的数据
|
|||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const exportLoading = ref(false) // 导出的加载中
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
@ -12,9 +12,9 @@
|
|||||||
v-model="formData.userIds"
|
v-model="formData.userIds"
|
||||||
:data="userTree"
|
:data="userTree"
|
||||||
:props="defaultProps"
|
:props="defaultProps"
|
||||||
check-on-click-node
|
|
||||||
multiple
|
multiple
|
||||||
filterable
|
filterable
|
||||||
|
check-on-click-node
|
||||||
node-key="id"
|
node-key="id"
|
||||||
placeholder="请选择规则适用人群"
|
placeholder="请选择规则适用人群"
|
||||||
/>
|
/>
|
||||||
@ -25,8 +25,8 @@
|
|||||||
:data="deptTree"
|
:data="deptTree"
|
||||||
:props="defaultProps"
|
:props="defaultProps"
|
||||||
multiple
|
multiple
|
||||||
check-strictly
|
|
||||||
filterable
|
filterable
|
||||||
|
check-strictly
|
||||||
node-key="id"
|
node-key="id"
|
||||||
placeholder="请选择规则适用部门"
|
placeholder="请选择规则适用部门"
|
||||||
/>
|
/>
|
||||||
|
@ -8,7 +8,11 @@
|
|||||||
<colum-info-form ref="columInfoRef" :columns="formData.columns" />
|
<colum-info-form ref="columInfoRef" :columns="formData.columns" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="生成信息" name="generateInfo">
|
<el-tab-pane label="生成信息" name="generateInfo">
|
||||||
<generate-info-form ref="generateInfoRef" :table="formData.table" :columns="formData.columns" />
|
<generate-info-form
|
||||||
|
ref="generateInfoRef"
|
||||||
|
:table="formData.table"
|
||||||
|
:columns="formData.columns"
|
||||||
|
/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<el-form>
|
<el-form>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
<el-table-column label="编号" align="center" prop="id" />
|
<el-table-column label="编号" align="center" prop="id" />
|
||||||
<el-table-column label="名字" align="center" prop="name" />
|
<el-table-column label="名字" align="center" prop="name" />
|
||||||
<el-table-column label="分数" align="center" prop="score" />
|
<el-table-column label="分数" align="center" prop="score" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
label="创建时间"
|
label="创建时间"
|
||||||
@ -49,8 +49,8 @@
|
|||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<Demo03CourseForm ref="formRef" @success="getList" />
|
<Demo03CourseForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
<el-table-column label="编号" align="center" prop="id" />
|
<el-table-column label="编号" align="center" prop="id" />
|
||||||
<el-table-column label="名字" align="center" prop="name" />
|
<el-table-column label="名字" align="center" prop="name" />
|
||||||
<el-table-column label="班主任" align="center" prop="teacher" />
|
<el-table-column label="班主任" align="center" prop="teacher" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
label="创建时间"
|
label="创建时间"
|
||||||
@ -49,8 +49,8 @@
|
|||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<Demo03GradeForm ref="formRef" @success="getList" />
|
<Demo03GradeForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
>
|
>
|
||||||
<el-table :data="formData" class="-mt-10px">
|
<el-table :data="formData" class="-mt-10px">
|
||||||
<el-table-column label="序号" type="index" width="100" />
|
<el-table-column label="序号" type="index" width="100" />
|
||||||
<el-table-column label="名字" min-width="150">
|
<el-table-column label="名字" min-width="150">
|
||||||
<template #default="{ row, $index }">
|
<template #default="{ row, $index }">
|
||||||
<el-form-item :prop="`${$index}.name`" :rules="formRules.name" class="mb-0px!">
|
<el-form-item :prop="`${$index}.name`" :rules="formRules.name" class="mb-0px!">
|
||||||
<el-input v-model="row.name" placeholder="请输入名字" />
|
<el-input v-model="row.name" placeholder="请输入名字" />
|
||||||
@ -57,7 +57,7 @@ watch(
|
|||||||
formData.value = []
|
formData.value = []
|
||||||
// 2. val 非空,则加载数据
|
// 2. val 非空,则加载数据
|
||||||
if (!val) {
|
if (!val) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
label-width="100px"
|
label-width="100px"
|
||||||
v-loading="formLoading"
|
v-loading="formLoading"
|
||||||
>
|
>
|
||||||
<el-form-item label="名字" prop="name">
|
<el-form-item label="名字" prop="name">
|
||||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="班主任" prop="teacher">
|
<el-form-item label="班主任" prop="teacher">
|
||||||
@ -38,11 +38,11 @@ watch(
|
|||||||
id: undefined,
|
id: undefined,
|
||||||
studentId: undefined,
|
studentId: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
teacher: undefined,
|
teacher: undefined
|
||||||
}
|
}
|
||||||
// 2. val 非空,则加载数据
|
// 2. val 非空,则加载数据
|
||||||
if (!val) {
|
if (!val) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
>
|
>
|
||||||
<el-table :data="formData" class="-mt-10px">
|
<el-table :data="formData" class="-mt-10px">
|
||||||
<el-table-column label="序号" type="index" width="100" />
|
<el-table-column label="序号" type="index" width="100" />
|
||||||
<el-table-column label="名字" min-width="150">
|
<el-table-column label="名字" min-width="150">
|
||||||
<template #default="{ row, $index }">
|
<template #default="{ row, $index }">
|
||||||
<el-form-item :prop="`${$index}.name`" :rules="formRules.name" class="mb-0px!">
|
<el-form-item :prop="`${$index}.name`" :rules="formRules.name" class="mb-0px!">
|
||||||
<el-input v-model="row.name" placeholder="请输入名字" />
|
<el-input v-model="row.name" placeholder="请输入名字" />
|
||||||
@ -57,7 +57,7 @@ watch(
|
|||||||
formData.value = []
|
formData.value = []
|
||||||
// 2. val 非空,则加载数据
|
// 2. val 非空,则加载数据
|
||||||
if (!val) {
|
if (!val) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
label-width="100px"
|
label-width="100px"
|
||||||
v-loading="formLoading"
|
v-loading="formLoading"
|
||||||
>
|
>
|
||||||
<el-form-item label="名字" prop="name">
|
<el-form-item label="名字" prop="name">
|
||||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="班主任" prop="teacher">
|
<el-form-item label="班主任" prop="teacher">
|
||||||
@ -38,11 +38,11 @@ watch(
|
|||||||
id: undefined,
|
id: undefined,
|
||||||
studentId: undefined,
|
studentId: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
teacher: undefined,
|
teacher: undefined
|
||||||
}
|
}
|
||||||
// 2. val 非空,则加载数据
|
// 2. val 非空,则加载数据
|
||||||
if (!val) {
|
if (!val) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
|
@ -20,8 +20,12 @@ import { propTypes } from '@/utils/propTypes'
|
|||||||
defineOptions({ name: 'ProductCategorySelect' })
|
defineOptions({ name: 'ProductCategorySelect' })
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: oneOfType([propTypes.number.def(undefined), propTypes.array.def([])]).def(undefined), // 选中的ID
|
// 选中的ID
|
||||||
multiple: propTypes.bool.def(false) // 是否多选
|
modelValue: oneOfType<number | number[]>([Number, Array<Number>]),
|
||||||
|
// 是否多选
|
||||||
|
multiple: propTypes.bool.def(false),
|
||||||
|
// 上级品类的编号
|
||||||
|
parentId: propTypes.number.def(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 选中的分类 ID */
|
/** 选中的分类 ID */
|
||||||
@ -38,10 +42,10 @@ const selectCategoryId = computed({
|
|||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
const categoryList = ref([]) // 分类树
|
const categoryList = ref<ProductCategoryApi.CategoryVO[]>([]) // 分类树
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 获得分类树
|
// 获得分类树
|
||||||
const data = await ProductCategoryApi.getCategoryList({})
|
const data = await ProductCategoryApi.getCategoryList({ parentId: props.parentId })
|
||||||
categoryList.value = handleTree(data, 'id', 'parentId')
|
categoryList.value = handleTree(data, 'id', 'parentId')
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user