فهرست منبع

去除无用文件

xiongxing 3 هفته پیش
والد
کامیت
a5e4f334ca
26فایلهای تغییر یافته به همراه517 افزوده شده و 3120 حذف شده
  1. 2 2
      .env
  2. 0 211
      src/components/AppLinkInput/AppLinkSelectDialog.vue
  3. 0 236
      src/components/AppLinkInput/data.ts
  4. 0 43
      src/components/AppLinkInput/index.vue
  5. 0 73
      src/components/DiyEditor/components/mobile/CouponCard/component.tsx
  6. 0 47
      src/components/DiyEditor/components/mobile/CouponCard/config.ts
  7. 0 149
      src/components/DiyEditor/components/mobile/CouponCard/index.vue
  8. 0 119
      src/components/DiyEditor/components/mobile/CouponCard/property.vue
  9. 0 97
      src/components/DiyEditor/components/mobile/ProductCard/config.ts
  10. 0 170
      src/components/DiyEditor/components/mobile/ProductCard/index.vue
  11. 0 149
      src/components/DiyEditor/components/mobile/ProductCard/property.vue
  12. 0 64
      src/components/DiyEditor/components/mobile/ProductList/config.ts
  13. 0 132
      src/components/DiyEditor/components/mobile/ProductList/index.vue
  14. 0 99
      src/components/DiyEditor/components/mobile/ProductList/property.vue
  15. 0 96
      src/components/DiyEditor/components/mobile/PromotionCombination/config.ts
  16. 0 201
      src/components/DiyEditor/components/mobile/PromotionCombination/index.vue
  17. 0 164
      src/components/DiyEditor/components/mobile/PromotionCombination/property.vue
  18. 0 96
      src/components/DiyEditor/components/mobile/PromotionPoint/config.ts
  19. 0 202
      src/components/DiyEditor/components/mobile/PromotionPoint/index.vue
  20. 0 154
      src/components/DiyEditor/components/mobile/PromotionPoint/property.vue
  21. 0 96
      src/components/DiyEditor/components/mobile/PromotionSeckill/config.ts
  22. 0 201
      src/components/DiyEditor/components/mobile/PromotionSeckill/index.vue
  23. 0 164
      src/components/DiyEditor/components/mobile/PromotionSeckill/property.vue
  24. 1 24
      src/router/modules/remaining.ts
  25. 10 10
      src/views/member/user/index.vue
  26. 504 121
      src/views/tenantManage/index.vue

+ 2 - 2
.env

@@ -8,8 +8,8 @@ VITE_PORT=80
 VITE_OPEN=true
 
 # 租户开关
-# VITE_APP_TENANT_ENABLE=true
-VITE_APP_TENANT_ENABLE=false
+VITE_APP_TENANT_ENABLE=true
+# VITE_APP_TENANT_ENABLE=false
 
 # 验证码的开关
 VITE_APP_CAPTCHA_ENABLE=true

+ 0 - 211
src/components/AppLinkInput/AppLinkSelectDialog.vue

@@ -1,211 +0,0 @@
-<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"
-            :show-after="300"
-          >
-            <el-button
-              class="m-b-8px m-r-8px m-l-0px!"
-              :type="isSameLink(appLink.path, activeAppLink.path) ? '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, AppLink } 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({} as AppLink)
-
-/** 打开弹窗 */
-const dialogVisible = ref(false)
-const open = (link: string) => {
-  // 进入页面时先重置 activeAppLink
-  activeAppLink.value = { name: '', path: '' }
-  dialogVisible.value = true
-
-  // 滚动到当前的链接
-  const group = APP_LINK_GROUP_LIST.find((group) =>
-    group.links.some((linkItem) => {
-      const sameLink = isSameLink(linkItem.path, link)
-      if (sameLink) {
-        activeAppLink.value = { ...linkItem, path: link }
-      }
-      return sameLink
-    })
-  )
-  if (group) {
-    // 使用 nextTick 的原因:可能 Dom 还没生成,导致滚动失败
-    nextTick(() => handleGroupSelected(group.name))
-  }
-}
-defineExpose({ open })
-
-// 处理 APP 链接选中
-const handleAppLinkSelected = (appLink: AppLink) => {
-  // 只有不同链接时才更新(避免重复触发)
-  if (!isSameLink(appLink.path, activeAppLink.value.path)) {
-    // 如果新链接的 path 为空,则沿用当前 activeAppLink 的 path
-    const path = appLink.path || activeAppLink.value.path
-    activeAppLink.value = { ...appLink, path: 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.path) || undefined
-      break
-    default:
-      break
-  }
-}
-
-// 处理绑定值更新
-const emit = defineEmits<{
-  change: [link: string]
-  appLinkChange: [appLink: AppLink]
-}>()
-const handleSubmit = () => {
-  dialogVisible.value = false
-  emit('change', activeAppLink.value.path)
-  emit('appLinkChange', activeAppLink.value)
-}
-
-// 分组标题引用列表
-const groupTitleRefs = ref<HTMLInputElement[]>([])
-/**
- * 处理右侧链接列表滚动
- * @param scrollTop 滚动条的位置
- */
-const handleScroll = ({ scrollTop }: { scrollTop: number }) => {
-  const titleEl = groupTitleRefs.value.find((titleEl: HTMLInputElement) => {
-    // 获取标题的位置信息
-    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: HTMLInputElement) => 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: ButtonInstance) => btn['ref'])
-    .find((ref: HTMLButtonElement) => 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.path, 'http://127.0.0.1')
-  // 修改 id 参数
-  url.searchParams.set('id', `${id}`)
-  // 排除域名
-  activeAppLink.value.path = `${url.pathname}${url.search}`
-  // 关闭对话框
-  detailSelectDialog.value.visible = false
-  // 重置 id
-  detailSelectDialog.value.id = undefined
-}
-</script>
-<style lang="scss" scoped></style>

+ 0 - 236
src/components/AppLinkInput/data.ts

@@ -1,236 +0,0 @@
-// APP 链接分组
-export interface AppLinkGroup {
-  // 分组名称
-  name: string
-  // 链接列表
-  links: AppLink[]
-}
-
-// APP 链接
-export interface AppLink {
-  // 链接名称
-  name: string
-  // 链接地址
-  path: string
-  // 链接的类型
-  type?: APP_LINK_TYPE_ENUM
-}
-
-// APP 链接类型(需要特殊处理,例如商品详情)
-export const enum APP_LINK_TYPE_ENUM {
-  // 拼团活动
-  ACTIVITY_COMBINATION,
-  // 秒杀活动
-  ACTIVITY_SECKILL,
-  // 积分商城活动
-  ACTIVITY_POINT,
-  // 文章详情
-  ARTICLE_DETAIL,
-  // 优惠券详情
-  COUPON_DETAIL,
-  // 自定义页面详情
-  DIY_PAGE_DETAIL,
-  // 品类列表
-  PRODUCT_CATEGORY_LIST,
-  // 商品列表
-  PRODUCT_LIST,
-  // 商品详情
-  PRODUCT_DETAIL_NORMAL,
-  // 拼团商品详情
-  PRODUCT_DETAIL_COMBINATION,
-  // 秒杀商品详情
-  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/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: '营销活动',
-    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/activity/point/list',
-        type: APP_LINK_TYPE_ENUM.ACTIVITY_POINT
-      },
-      {
-        name: '签到中心',
-        path: '/pages/app/sign'
-      },
-      {
-        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/goods'
-      },
-      {
-        name: '分销订单',
-        path: '/pages/commission/order'
-      },
-      {
-        name: '我的团队',
-        path: '/pages/commission/team'
-      }
-    ]
-  },
-  {
-    name: '支付',
-    links: [
-      {
-        name: '充值余额',
-        path: '/pages/pay/recharge'
-      },
-      {
-        name: '充值记录',
-        path: '/pages/pay/recharge-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/wallet/commission'
-      },
-      {
-        name: '用户余额',
-        path: '/pages/user/wallet/money'
-      },
-      {
-        name: '用户积分',
-        path: '/pages/user/wallet/score'
-      }
-    ]
-  }
-] as AppLinkGroup[]

+ 0 - 43
src/components/AppLinkInput/index.vue

@@ -1,43 +0,0 @@
-<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.value,
-  () => emit('update:modelValue', appLink.value)
-)
-</script>

+ 0 - 73
src/components/DiyEditor/components/mobile/CouponCard/component.tsx

@@ -1,73 +0,0 @@
-import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate'
-import { CouponTemplateValidityTypeEnum, PromotionDiscountTypeEnum } from '@/utils/constants'
-import { floatToFixed2 } from '@/utils'
-import { formatDate } from '@/utils/formatTime'
-import { object } from 'vue-types'
-
-// 优惠值
-export const CouponDiscount = defineComponent({
-  name: 'CouponDiscount',
-  props: {
-    coupon: object<CouponTemplateApi.CouponTemplateVO>()
-  },
-  setup(props) {
-    const coupon = props.coupon as CouponTemplateApi.CouponTemplateVO
-    // 折扣
-    let value = coupon.discountPercent / 10 + ''
-    let suffix = ' 折'
-    // 满减
-    if (coupon.discountType === PromotionDiscountTypeEnum.PRICE.type) {
-      value = floatToFixed2(coupon.discountPrice)
-      suffix = ' 元'
-    }
-    return () => (
-      <div>
-        <span class={'text-20px font-bold'}>{value}</span>
-        <span>{suffix}</span>
-      </div>
-    )
-  }
-})
-
-// 优惠描述
-export const CouponDiscountDesc = defineComponent({
-  name: 'CouponDiscountDesc',
-  props: {
-    coupon: object<CouponTemplateApi.CouponTemplateVO>()
-  },
-  setup(props) {
-    const coupon = props.coupon as CouponTemplateApi.CouponTemplateVO
-    // 使用条件
-    const useCondition = coupon.usePrice > 0 ? `满${floatToFixed2(coupon.usePrice)}元,` : ''
-    // 优惠描述
-    const discountDesc =
-      coupon.discountType === PromotionDiscountTypeEnum.PRICE.type
-        ? `减${floatToFixed2(coupon.discountPrice)}元`
-        : `打${coupon.discountPercent / 10.0}折`
-    return () => (
-      <div>
-        <span>{useCondition}</span>
-        <span>{discountDesc}</span>
-      </div>
-    )
-  }
-})
-
-// 有效期
-export const CouponValidTerm = defineComponent({
-  name: 'CouponValidTerm',
-  props: {
-    coupon: object<CouponTemplateApi.CouponTemplateVO>()
-  },
-  setup(props) {
-    const coupon = props.coupon as CouponTemplateApi.CouponTemplateVO
-    const text =
-      coupon.validityType === CouponTemplateValidityTypeEnum.DATE.type
-        ? `有效期:${formatDate(coupon.validStartTime, 'YYYY-MM-DD')} 至 ${formatDate(
-            coupon.validEndTime,
-            'YYYY-MM-DD'
-          )}`
-        : `领取后第 ${coupon.fixedStartTerm} - ${coupon.fixedEndTerm} 天内可用`
-    return () => <div>{text}</div>
-  }
-})

+ 0 - 47
src/components/DiyEditor/components/mobile/CouponCard/config.ts

@@ -1,47 +0,0 @@
-import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
-
-/** 商品卡片属性 */
-export interface CouponCardProperty {
-  // 列数
-  columns: number
-  // 背景图
-  bgImg: string
-  // 文字颜色
-  textColor: string
-  // 按钮样式
-  button: {
-    // 颜色
-    color: string
-    // 背景颜色
-    bgColor: string
-  }
-  // 间距
-  space: number
-  // 优惠券编号列表
-  couponIds: number[]
-  // 组件样式
-  style: ComponentStyle
-}
-
-// 定义组件
-export const component = {
-  id: 'CouponCard',
-  name: '优惠券',
-  icon: 'ep:ticket',
-  property: {
-    columns: 1,
-    bgImg: '',
-    textColor: '#E9B461',
-    button: {
-      color: '#434343',
-      bgColor: ''
-    },
-    space: 0,
-    couponIds: [],
-    style: {
-      bgType: 'color',
-      bgColor: '',
-      marginBottom: 8
-    } as ComponentStyle
-  }
-} as DiyComponent<CouponCardProperty>

+ 0 - 149
src/components/DiyEditor/components/mobile/CouponCard/index.vue

@@ -1,149 +0,0 @@
-<template>
-  <el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
-    <div
-      class="flex flex-row text-12px"
-      :style="{
-        gap: `${property.space}px`,
-        width: scrollbarWidth
-      }"
-    >
-      <div
-        class="box-content"
-        :style="{
-          background: property.bgImg
-            ? `url(${property.bgImg}) 100% center / 100% 100% no-repeat`
-            : '#fff',
-          width: `${couponWidth}px`,
-          color: property.textColor
-        }"
-        v-for="(coupon, index) in couponList"
-        :key="index"
-      >
-        <!-- 布局1:1列-->
-        <div v-if="property.columns === 1" class="m-l-16px flex flex-row justify-between p-8px">
-          <div class="flex flex-col justify-evenly gap-4px">
-            <!-- 优惠值 -->
-            <CouponDiscount :coupon="coupon" />
-            <!-- 优惠描述 -->
-            <CouponDiscountDesc :coupon="coupon" />
-            <!-- 有效期 -->
-            <CouponValidTerm :coupon="coupon" />
-          </div>
-          <div class="flex flex-col justify-evenly">
-            <div
-              class="rounded-20px p-x-8px p-y-2px"
-              :style="{
-                color: property.button.color,
-                background: property.button.bgColor
-              }"
-            >
-              立即领取
-            </div>
-          </div>
-        </div>
-        <!-- 布局2:2列-->
-        <div
-          v-else-if="property.columns === 2"
-          class="m-l-16px flex flex-row justify-between p-8px"
-        >
-          <div class="flex flex-col justify-evenly gap-4px">
-            <!-- 优惠值 -->
-            <CouponDiscount :coupon="coupon" />
-            <!-- 优惠描述 -->
-            <CouponDiscountDesc :coupon="coupon" />
-            <!-- 领取说明 -->
-            <div v-if="coupon.totalCount >= 0">
-              仅剩:{{ coupon.totalCount - coupon.takeCount }}张
-            </div>
-            <div v-else-if="coupon.totalCount === -1">仅剩:不限制</div>
-          </div>
-          <div class="flex flex-col">
-            <div
-              class="h-full w-20px rounded-20px p-x-2px p-y-8px text-center"
-              :style="{
-                color: property.button.color,
-                background: property.button.bgColor
-              }"
-            >
-              立即领取
-            </div>
-          </div>
-        </div>
-        <!-- 布局3:3列-->
-        <div v-else class="flex flex-col items-center justify-around gap-4px p-4px">
-          <!-- 优惠值 -->
-          <CouponDiscount :coupon="coupon" />
-          <!-- 优惠描述 -->
-          <CouponDiscountDesc :coupon="coupon" />
-          <div
-            class="rounded-20px p-x-8px p-y-2px"
-            :style="{
-              color: property.button.color,
-              background: property.button.bgColor
-            }"
-          >
-            立即领取
-          </div>
-        </div>
-      </div>
-    </div>
-  </el-scrollbar>
-</template>
-<script setup lang="ts">
-import { CouponCardProperty } from './config'
-import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate'
-import { CouponDiscount } from './component'
-import {
-  CouponDiscountDesc,
-  CouponValidTerm
-} from '@/components/DiyEditor/components/mobile/CouponCard/component'
-
-/** 商品卡片 */
-defineOptions({ name: 'CouponCard' })
-// 定义属性
-const props = defineProps<{ property: CouponCardProperty }>()
-// 商品列表
-const couponList = ref<CouponTemplateApi.CouponTemplateVO[]>([])
-watch(
-  () => props.property.couponIds,
-  async () => {
-    if (props.property.couponIds?.length > 0) {
-      couponList.value = await CouponTemplateApi.getCouponTemplateList(props.property.couponIds)
-    }
-  },
-  {
-    immediate: true,
-    deep: true
-  }
-)
-
-// 手机宽度
-const phoneWidth = ref(375)
-// 容器
-const containerRef = ref()
-// 滚动条宽度
-const scrollbarWidth = ref('100%')
-// 优惠券的宽度
-const couponWidth = ref(375)
-// 计算布局参数
-watch(
-  () => [props.property, phoneWidth, couponList.value.length],
-  () => {
-    // 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
-    couponWidth.value =
-      (phoneWidth.value - props.property.space * (props.property.columns - 1)) /
-      props.property.columns
-    // 显示滚动条
-    scrollbarWidth.value = `${
-      couponWidth.value * couponList.value.length +
-      props.property.space * (couponList.value.length - 1)
-    }px`
-  },
-  { immediate: true, deep: true }
-)
-onMounted(() => {
-  // 提取手机宽度
-  phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375
-})
-</script>
-<style scoped lang="scss"></style>

+ 0 - 119
src/components/DiyEditor/components/mobile/CouponCard/property.vue

@@ -1,119 +0,0 @@
-<template>
-  <ComponentContainerProperty v-model="formData.style">
-    <el-form label-width="80px" :model="formData">
-      <el-card header="优惠券列表" class="property-group" shadow="never">
-        <div
-          v-for="(coupon, index) in couponList"
-          :key="index"
-          class="flex items-center justify-between"
-        >
-          <el-text size="large" truncated>{{ coupon.name }}</el-text>
-          <el-text type="info" truncated>
-            <span v-if="coupon.usePrice > 0">满{{ floatToFixed2(coupon.usePrice) }}元,</span>
-            <span v-if="coupon.discountType === PromotionDiscountTypeEnum.PRICE.type">
-              减{{ floatToFixed2(coupon.discountPrice) }}元
-            </span>
-            <span v-else> 打{{ coupon.discountPercent }}折 </span>
-          </el-text>
-        </div>
-        <el-form-item label-width="0">
-          <el-button @click="handleAddCoupon" type="primary" plain class="m-t-8px w-full">
-            <Icon icon="ep:plus" class="mr-5px" /> 添加
-          </el-button>
-        </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.columns">
-            <el-tooltip class="item" content="一列" placement="bottom">
-              <el-radio-button :value="1">
-                <Icon icon="fluent:text-column-one-24-filled" />
-              </el-radio-button>
-            </el-tooltip>
-            <el-tooltip class="item" content="二列" placement="bottom">
-              <el-radio-button :value="2">
-                <Icon icon="fluent:text-column-two-24-filled" />
-              </el-radio-button>
-            </el-tooltip>
-            <el-tooltip class="item" content="三列" placement="bottom">
-              <el-radio-button :value="3">
-                <Icon icon="fluent:text-column-three-24-filled" />
-              </el-radio-button>
-            </el-tooltip>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="背景图片" prop="bgImg">
-          <UploadImg v-model="formData.bgImg" height="80px" width="100%" class="min-w-160px" />
-        </el-form-item>
-        <el-form-item label="文字颜色" prop="textColor">
-          <ColorInput v-model="formData.textColor" />
-        </el-form-item>
-        <el-form-item label="按钮背景" prop="button.bgColor">
-          <ColorInput v-model="formData.button.bgColor" />
-        </el-form-item>
-        <el-form-item label="按钮文字" prop="button.color">
-          <ColorInput v-model="formData.button.color" />
-        </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>
-  <!-- 优惠券选择 -->
-  <CouponSelect
-    ref="couponSelectDialog"
-    v-model:multiple-selection="couponList"
-    :take-type="CouponTemplateTakeTypeEnum.USER.type"
-    @change="handleCouponSelect"
-  />
-</template>
-
-<script setup lang="ts">
-import { CouponCardProperty } from './config'
-import { useVModel } from '@vueuse/core'
-import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate'
-import { floatToFixed2 } from '@/utils'
-import { CouponTemplateTakeTypeEnum, PromotionDiscountTypeEnum } from '@/utils/constants'
-import CouponSelect from '@/views/mall/promotion/coupon/components/CouponSelect.vue'
-
-// 优惠券卡片属性面板
-defineOptions({ name: 'CouponCardProperty' })
-
-const props = defineProps<{ modelValue: CouponCardProperty }>()
-const emit = defineEmits(['update:modelValue'])
-const formData = useVModel(props, 'modelValue', emit)
-
-// 优惠券列表
-const couponList = ref<CouponTemplateApi.CouponTemplateVO[]>([])
-const couponSelectDialog = ref()
-// 添加优惠券
-const handleAddCoupon = () => {
-  couponSelectDialog.value.open()
-}
-const handleCouponSelect = () => {
-  formData.value.couponIds = couponList.value.map((coupon) => coupon.id)
-}
-
-watch(
-  () => formData.value.couponIds,
-  async () => {
-    if (formData.value.couponIds?.length > 0) {
-      couponList.value = await CouponTemplateApi.getCouponTemplateList(formData.value.couponIds)
-    }
-  },
-  {
-    immediate: true,
-    deep: true
-  }
-)
-</script>
-
-<style scoped lang="scss"></style>

+ 0 - 97
src/components/DiyEditor/components/mobile/ProductCard/config.ts

@@ -1,97 +0,0 @@
-import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
-
-/** 商品卡片属性 */
-export interface ProductCardProperty {
-  // 布局类型:单列大图 | 单列小图 | 双列
-  layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'
-  // 商品字段
-  fields: {
-    // 商品名称
-    name: ProductCardFieldProperty
-    // 商品简介
-    introduction: ProductCardFieldProperty
-    // 商品价格
-    price: ProductCardFieldProperty
-    // 商品市场价
-    marketPrice: ProductCardFieldProperty
-    // 商品销量
-    salesCount: ProductCardFieldProperty
-    // 商品库存
-    stock: ProductCardFieldProperty
-  }
-  // 角标
-  badge: {
-    // 是否显示
-    show: boolean
-    // 角标图片
-    imgUrl: string
-  }
-  // 按钮
-  btnBuy: {
-    // 类型:文字 | 图片
-    type: 'text' | 'img'
-    // 文字
-    text: string
-    // 文字按钮:背景渐变起始颜色
-    bgBeginColor: string
-    // 文字按钮:背景渐变结束颜色
-    bgEndColor: string
-    // 图片按钮:图片地址
-    imgUrl: string
-  }
-  // 上圆角
-  borderRadiusTop: number
-  // 下圆角
-  borderRadiusBottom: number
-  // 间距
-  space: number
-  // 商品编号列表
-  spuIds: number[]
-  // 组件样式
-  style: ComponentStyle
-}
-// 商品字段
-export interface ProductCardFieldProperty {
-  // 是否显示
-  show: boolean
-  // 颜色
-  color: string
-}
-
-// 定义组件
-export const component = {
-  id: 'ProductCard',
-  name: '商品卡片',
-  icon: 'fluent:text-column-two-left-24-filled',
-  property: {
-    layoutType: 'oneColBigImg',
-    fields: {
-      name: { show: true, color: '#000' },
-      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: '' },
-    btnBuy: {
-      type: 'text',
-      text: '立即购买',
-      // todo: @owen 根据主题色配置
-      bgBeginColor: '#FF6000',
-      bgEndColor: '#FE832A',
-      imgUrl: ''
-    },
-    borderRadiusTop: 6,
-    borderRadiusBottom: 6,
-    space: 8,
-    spuIds: [],
-    style: {
-      bgType: 'color',
-      bgColor: '',
-      marginLeft: 8,
-      marginRight: 8,
-      marginBottom: 8
-    } as ComponentStyle
-  }
-} as DiyComponent<ProductCardProperty>

+ 0 - 170
src/components/DiyEditor/components/mobile/ProductCard/index.vue

@@ -1,170 +0,0 @@
-<template>
-  <div :class="`box-content min-h-30px w-full flex flex-row flex-wrap`" ref="containerRef">
-    <div
-      class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
-      :style="{
-        ...calculateSpace(index),
-        ...calculateWidth(),
-        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 && property.badge.imgUrl"
-        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
-        :class="[
-          'h-140px',
-          {
-            'w-full': property.layoutType !== 'oneColSmallImg',
-            'w-140px': property.layoutType === 'oneColSmallImg'
-          }
-        ]"
-      >
-        <el-image fit="cover" class="h-full w-full" :src="spu.picUrl" />
-      </div>
-      <div
-        :class="[
-          ' flex flex-col gap-8px p-8px box-border',
-          {
-            'w-full': property.layoutType !== 'oneColSmallImg',
-            'w-[calc(100%-140px-16px)]': property.layoutType === 'oneColSmallImg'
-          }
-        ]"
-      >
-        <!-- 商品名称 -->
-        <div
-          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 }"
-        >
-          {{ spu.name }}
-        </div>
-        <!-- 商品简介 -->
-        <div
-          v-if="property.fields.introduction.show"
-          class="truncate text-12px"
-          :style="{ color: property.fields.introduction.color }"
-        >
-          {{ spu.introduction }}
-        </div>
-        <div>
-          <!-- 价格 -->
-          <span
-            v-if="property.fields.price.show"
-            class="text-16px"
-            :style="{ color: property.fields.price.color }"
-          >
-            ¥{{ fenToYuan(spu.price as any) }}
-          </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>
-        </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>
-</template>
-<script setup lang="ts">
-import { ProductCardProperty } from './config'
-import * as ProductSpuApi from '@/api/mall/product/spu'
-import { fenToYuan } from '../../../../../utils'
-
-/** 商品卡片 */
-defineOptions({ name: 'ProductCard' })
-// 定义属性
-const props = defineProps<{ property: ProductCardProperty }>()
-// 商品列表
-const spuList = ref<ProductSpuApi.Spu[]>([])
-watch(
-  () => props.property.spuIds,
-  async () => {
-    spuList.value = await ProductSpuApi.getSpuDetailList(props.property.spuIds)
-  },
-  {
-    immediate: true,
-    deep: true
-  }
-)
-
-/**
- * 计算商品的间距
- * @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 calculateWidth = () => {
-  let width = '100%'
-  // 双列时每列的宽度为:(总宽度 - 间距)/ 2
-  if (props.property.layoutType === 'twoCol') {
-    width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`
-  }
-  return { width }
-}
-</script>
-
-<style scoped lang="scss"></style>

+ 0 - 149
src/components/DiyEditor/components/mobile/ProductCard/property.vue

@@ -1,149 +0,0 @@
-<template>
-  <ComponentContainerProperty v-model="formData.style">
-    <el-form label-width="80px" :model="formData">
-      <el-card header="商品列表" class="property-group" shadow="never">
-        <SpuShowcase v-model="formData.spuIds" />
-      </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 value="oneColBigImg">
-                <Icon icon="fluent:text-column-one-24-filled" />
-              </el-radio-button>
-            </el-tooltip>
-            <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-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.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">
-          <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-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 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="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>
-          </el-form-item>
-        </template>
-      </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 { ProductCardProperty } from './config'
-import { useVModel } from '@vueuse/core'
-import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
-
-// 商品卡片属性面板
-defineOptions({ name: 'ProductCardProperty' })
-
-const props = defineProps<{ modelValue: ProductCardProperty }>()
-const emit = defineEmits(['update:modelValue'])
-const formData = useVModel(props, 'modelValue', emit)
-</script>
-
-<style scoped lang="scss"></style>

+ 0 - 64
src/components/DiyEditor/components/mobile/ProductList/config.ts

@@ -1,64 +0,0 @@
-import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
-
-/** 商品栏属性 */
-export interface ProductListProperty {
-  // 布局类型:双列 | 三列 | 水平滑动
-  layoutType: 'twoCol' | 'threeCol' | 'horizSwiper'
-  // 商品字段
-  fields: {
-    // 商品名称
-    name: ProductListFieldProperty
-    // 商品价格
-    price: ProductListFieldProperty
-  }
-  // 角标
-  badge: {
-    // 是否显示
-    show: boolean
-    // 角标图片
-    imgUrl: string
-  }
-  // 上圆角
-  borderRadiusTop: number
-  // 下圆角
-  borderRadiusBottom: number
-  // 间距
-  space: number
-  // 商品编号列表
-  spuIds: number[]
-  // 组件样式
-  style: ComponentStyle
-}
-// 商品字段
-export interface ProductListFieldProperty {
-  // 是否显示
-  show: boolean
-  // 颜色
-  color: string
-}
-
-// 定义组件
-export const component = {
-  id: 'ProductList',
-  name: '商品栏',
-  icon: 'fluent:text-column-two-24-filled',
-  property: {
-    layoutType: 'twoCol',
-    fields: {
-      name: { show: true, color: '#000' },
-      price: { show: true, color: '#ff3000' }
-    },
-    badge: { show: false, imgUrl: '' },
-    borderRadiusTop: 8,
-    borderRadiusBottom: 8,
-    space: 8,
-    spuIds: [],
-    style: {
-      bgType: 'color',
-      bgColor: '',
-      marginLeft: 8,
-      marginRight: 8,
-      marginBottom: 8
-    } as ComponentStyle
-  }
-} as DiyComponent<ProductListProperty>

+ 0 - 132
src/components/DiyEditor/components/mobile/ProductList/index.vue

@@ -1,132 +0,0 @@
-<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 }"
-            >
-              ¥{{ fenToYuan(spu.price) }}
-            </span>
-          </div>
-        </div>
-      </div>
-    </div>
-  </el-scrollbar>
-</template>
-<script setup lang="ts">
-import { ProductListProperty } from './config'
-import * as ProductSpuApi from '@/api/mall/product/spu'
-import { fenToYuan } from '@/utils'
-
-/** 商品栏 */
-defineOptions({ name: 'ProductList' })
-// 定义属性
-const props = defineProps<{ property: ProductListProperty }>()
-// 商品列表
-const spuList = ref<ProductSpuApi.Spu[]>([])
-watch(
-  () => props.property.spuIds,
-  async () => {
-    spuList.value = await ProductSpuApi.getSpuDetailList(props.property.spuIds)
-  },
-  {
-    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 === 'twoCol' ? 2 : 3
-    // 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
-    const productWidth =
-      (phoneWidth.value - props.property.space * (columns.value - 1)) / columns.value
-    // 商品图布局:2列时,左右布局 3列时,上下布局
-    imageSize.value = columns.value === 2 ? '64px' : `${productWidth}px`
-    // 根据布局类型,计算行数、列数
-    if (props.property.layoutType === 'horizSwiper') {
-      // 单行显示
-      gridTemplateColumns.value = `repeat(auto-fill, ${productWidth}px)`
-      // 显示滚动条
-      scrollbarWidth.value = `${
-        productWidth * spuList.value.length + props.property.space * (spuList.value.length - 1)
-      }px`
-    } else {
-      // 指定列数
-      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 - 99
src/components/DiyEditor/components/mobile/ProductList/property.vue

@@ -1,99 +0,0 @@
-<template>
-  <ComponentContainerProperty v-model="formData.style">
-    <el-form label-width="80px" :model="formData">
-      <el-card header="商品列表" class="property-group" shadow="never">
-        <SpuShowcase v-model="formData.spuIds" />
-      </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 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">
-                <Icon icon="fluent:text-column-three-24-filled" />
-              </el-radio-button>
-            </el-tooltip>
-            <el-tooltip class="item" content="水平滑动" placement="bottom">
-              <el-radio-button value="horizSwiper">
-                <Icon icon="system-uicons:carousel" />
-              </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 { ProductListProperty } from './config'
-import { useVModel } from '@vueuse/core'
-import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
-
-// 商品栏属性面板
-defineOptions({ name: 'ProductListProperty' })
-
-const props = defineProps<{ modelValue: ProductListProperty }>()
-const emit = defineEmits(['update:modelValue'])
-const formData = useVModel(props, 'modelValue', emit)
-</script>
-
-<style scoped lang="scss"></style>

+ 0 - 96
src/components/DiyEditor/components/mobile/PromotionCombination/config.ts

@@ -1,96 +0,0 @@
-import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
-
-/** 拼团属性 */
-export interface PromotionCombinationProperty {
-  // 布局类型:单列 | 三列
-  layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'
-  // 商品字段
-  fields: {
-    // 商品名称
-    name: PromotionCombinationFieldProperty
-    // 商品简介
-    introduction: PromotionCombinationFieldProperty
-    // 商品价格
-    price: PromotionCombinationFieldProperty
-    // 市场价
-    marketPrice: PromotionCombinationFieldProperty
-    // 商品销量
-    salesCount: PromotionCombinationFieldProperty
-    // 商品库存
-    stock: PromotionCombinationFieldProperty
-  }
-  // 角标
-  badge: {
-    // 是否显示
-    show: boolean
-    // 角标图片
-    imgUrl: string
-  }
-  // 按钮
-  btnBuy: {
-    // 类型:文字 | 图片
-    type: 'text' | 'img'
-    // 文字
-    text: string
-    // 文字按钮:背景渐变起始颜色
-    bgBeginColor: string
-    // 文字按钮:背景渐变结束颜色
-    bgEndColor: string
-    // 图片按钮:图片地址
-    imgUrl: string
-  }
-  // 上圆角
-  borderRadiusTop: number
-  // 下圆角
-  borderRadiusBottom: number
-  // 间距
-  space: number
-  // 拼团活动编号
-  activityIds: number[]
-  // 组件样式
-  style: ComponentStyle
-}
-
-// 商品字段
-export interface PromotionCombinationFieldProperty {
-  // 是否显示
-  show: boolean
-  // 颜色
-  color: string
-}
-
-// 定义组件
-export const component = {
-  id: 'PromotionCombination',
-  name: '拼团',
-  icon: 'mdi:account-group',
-  property: {
-    layoutType: 'oneColBigImg',
-    fields: {
-      name: { show: true, color: '#000' },
-      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: '' },
-    btnBuy: {
-      type: 'text',
-      text: '去拼团',
-      bgBeginColor: '#FF6000',
-      bgEndColor: '#FE832A',
-      imgUrl: ''
-    },
-    borderRadiusTop: 8,
-    borderRadiusBottom: 8,
-    space: 8,
-    style: {
-      bgType: 'color',
-      bgColor: '',
-      marginLeft: 8,
-      marginRight: 8,
-      marginBottom: 8
-    } as ComponentStyle
-  }
-} as DiyComponent<PromotionCombinationProperty>

+ 0 - 201
src/components/DiyEditor/components/mobile/PromotionCombination/index.vue

@@ -1,201 +0,0 @@
-<template>
-  <div :class="`box-content min-h-30px w-full flex flex-row flex-wrap`" ref="containerRef">
-    <div
-      class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
-      :style="{
-        ...calculateSpace(index),
-        ...calculateWidth(),
-        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>
-      <!-- 商品封面图 -->
-      <div
-        :class="[
-          'h-140px',
-          {
-            'w-full': property.layoutType !== 'oneColSmallImg',
-            'w-140px': property.layoutType === 'oneColSmallImg'
-          }
-        ]"
-      >
-        <el-image fit="cover" class="h-full w-full" :src="spu.picUrl" />
-      </div>
-      <div
-        :class="[
-          ' flex flex-col gap-8px p-8px box-border',
-          {
-            'w-full': property.layoutType !== 'oneColSmallImg',
-            'w-[calc(100%-140px-16px)]': property.layoutType === 'oneColSmallImg'
-          }
-        ]"
-      >
-        <!-- 商品名称 -->
-        <div
-          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 }"
-        >
-          {{ spu.name }}
-        </div>
-        <!-- 商品简介 -->
-        <div
-          v-if="property.fields.introduction.show"
-          class="truncate text-12px"
-          :style="{ color: property.fields.introduction.color }"
-        >
-          {{ spu.introduction }}
-        </div>
-        <div>
-          <!-- 价格 -->
-          <span
-            v-if="property.fields.price.show"
-            class="text-16px"
-            :style="{ color: property.fields.price.color }"
-          >
-            ¥{{ 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>
-        </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>
-</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'
-import { fenToYuan } from '@/utils'
-
-/** 拼团卡片 */
-defineOptions({ name: 'PromotionCombination' })
-// 定义属性
-const props = defineProps<{ property: PromotionCombinationProperty }>()
-// 商品列表
-const spuList = ref<ProductSpuApi.Spu[]>([])
-const spuIdList = ref<number[]>([])
-const combinationActivityList = ref<CombinationActivityApi.CombinationActivityVO[]>([])
-
-watch(
-  () => props.property.activityIds,
-  async () => {
-    try {
-      // 新添加的拼团组件,是没有活动ID的
-      const activityIds = props.property.activityIds
-      // 检查活动ID的有效性
-      if (Array.isArray(activityIds) && activityIds.length > 0) {
-        // 获取拼团活动详情列表
-        combinationActivityList.value =
-          await CombinationActivityApi.getCombinationActivityListByIds(activityIds)
-
-        // 获取拼团活动的 SPU 详情列表
-        spuList.value = []
-        spuIdList.value = combinationActivityList.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 的最低价格
-        combinationActivityList.value.forEach((activity) => {
-          // 匹配spuId
-          const spu = spuList.value.find((spu) => spu.id === activity.spuId)
-          if (spu) {
-            // 赋值活动价格,哪个最便宜就赋值哪个
-            spu.price = Math.min(activity.combinationPrice || Infinity, spu.price || Infinity)
-          }
-        })
-      }
-    } catch (error) {
-      console.error('获取拼团活动细节或 SPU 细节时出错:', error)
-    }
-  },
-  {
-    immediate: true,
-    deep: true
-  }
-)
-
-/**
- * 计算商品的间距
- * @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 calculateWidth = () => {
-  let width = '100%'
-  // 双列时每列的宽度为:(总宽度 - 间距)/ 2
-  if (props.property.layoutType === 'twoCol') {
-    width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`
-  }
-  return { width }
-}
-</script>
-
-<style scoped lang="scss"></style>

+ 0 - 164
src/components/DiyEditor/components/mobile/PromotionCombination/property.vue

@@ -1,164 +0,0 @@
-<template>
-  <ComponentContainerProperty v-model="formData.style">
-    <el-form label-width="80px" :model="formData">
-      <el-card header="拼团活动" class="property-group" shadow="never">
-        <CombinationShowcase v-model="formData.activityIds" />
-      </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 value="oneColBigImg">
-                <Icon icon="fluent:text-column-one-24-filled" />
-              </el-radio-button>
-            </el-tooltip>
-            <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">
-                <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.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">
-          <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-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 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="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>
-          </el-form-item>
-        </template>
-      </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 { useVModel } from '@vueuse/core'
-import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
-import { CommonStatusEnum } from '@/utils/constants'
-import CombinationShowcase from '@/views/mall/promotion/combination/components/CombinationShowcase.vue'
-
-// 拼团属性面板
-defineOptions({ name: 'PromotionCombinationProperty' })
-
-const props = defineProps<{ modelValue: PromotionCombinationProperty }>()
-const emit = defineEmits(['update:modelValue'])
-const formData = useVModel(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 - 96
src/components/DiyEditor/components/mobile/PromotionPoint/config.ts

@@ -1,96 +0,0 @@
-import {ComponentStyle, DiyComponent} from '@/components/DiyEditor/util'
-
-/** 积分商城属性 */
-export interface PromotionPointProperty {
-  // 布局类型:单列 | 三列
-  layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'
-  // 商品字段
-  fields: {
-    // 商品名称
-    name: PromotionPointFieldProperty
-    // 商品简介
-    introduction: PromotionPointFieldProperty
-    // 商品价格
-    price: PromotionPointFieldProperty
-    // 市场价
-    marketPrice: PromotionPointFieldProperty
-    // 商品销量
-    salesCount: PromotionPointFieldProperty
-    // 商品库存
-    stock: PromotionPointFieldProperty
-  }
-  // 角标
-  badge: {
-    // 是否显示
-    show: boolean
-    // 角标图片
-    imgUrl: string
-  }
-  // 按钮
-  btnBuy: {
-    // 类型:文字 | 图片
-    type: 'text' | 'img'
-    // 文字
-    text: string
-    // 文字按钮:背景渐变起始颜色
-    bgBeginColor: string
-    // 文字按钮:背景渐变结束颜色
-    bgEndColor: string
-    // 图片按钮:图片地址
-    imgUrl: string
-  }
-  // 上圆角
-  borderRadiusTop: number
-  // 下圆角
-  borderRadiusBottom: number
-  // 间距
-  space: number
-  // 秒杀活动编号
-  activityIds: number[]
-  // 组件样式
-  style: ComponentStyle
-}
-
-// 商品字段
-export interface PromotionPointFieldProperty {
-  // 是否显示
-  show: boolean
-  // 颜色
-  color: string
-}
-
-// 定义组件
-export const component = {
-  id: 'PromotionPoint',
-  name: '积分商城',
-  icon: 'ep:present',
-  property: {
-    layoutType: 'oneColBigImg',
-    fields: {
-      name: { show: true, color: '#000' },
-      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: '' },
-    btnBuy: {
-      type: 'text',
-      text: '立即兑换',
-      bgBeginColor: '#FF6000',
-      bgEndColor: '#FE832A',
-      imgUrl: ''
-    },
-    borderRadiusTop: 8,
-    borderRadiusBottom: 8,
-    space: 8,
-    style: {
-      bgType: 'color',
-      bgColor: '',
-      marginLeft: 8,
-      marginRight: 8,
-      marginBottom: 8
-    } as ComponentStyle
-  }
-} as DiyComponent<PromotionPointProperty>

+ 0 - 202
src/components/DiyEditor/components/mobile/PromotionPoint/index.vue

@@ -1,202 +0,0 @@
-<template>
-  <div ref="containerRef" :class="`box-content min-h-30px w-full flex flex-row flex-wrap`">
-    <div
-      v-for="(spu, index) in spuList"
-      :key="index"
-      :style="{
-        ...calculateSpace(index),
-        ...calculateWidth(),
-        borderTopLeftRadius: `${property.borderRadiusTop}px`,
-        borderTopRightRadius: `${property.borderRadiusTop}px`,
-        borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
-        borderBottomRightRadius: `${property.borderRadiusBottom}px`
-      }"
-      class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
-    >
-      <!-- 角标 -->
-      <div v-if="property.badge.show" class="absolute left-0 top-0 z-1 items-center justify-center">
-        <el-image :src="property.badge.imgUrl" class="h-26px w-38px" fit="cover" />
-      </div>
-      <!-- 商品封面图 -->
-      <div
-        :class="[
-          'h-140px',
-          {
-            'w-full': property.layoutType !== 'oneColSmallImg',
-            'w-140px': property.layoutType === 'oneColSmallImg'
-          }
-        ]"
-      >
-        <el-image :src="spu.picUrl" class="h-full w-full" fit="cover" />
-      </div>
-      <div
-        :class="[
-          ' flex flex-col gap-8px p-8px box-border',
-          {
-            'w-full': property.layoutType !== 'oneColSmallImg',
-            'w-[calc(100%-140px-16px)]': property.layoutType === 'oneColSmallImg'
-          }
-        ]"
-      >
-        <!-- 商品名称 -->
-        <div
-          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 }"
-        >
-          {{ spu.name }}
-        </div>
-        <!-- 商品简介 -->
-        <div
-          v-if="property.fields.introduction.show"
-          :style="{ color: property.fields.introduction.color }"
-          class="truncate text-12px"
-        >
-          {{ spu.introduction }}
-        </div>
-        <div>
-          <!-- 积分 -->
-          <span
-            v-if="property.fields.price.show"
-            :style="{ color: property.fields.price.color }"
-            class="text-16px"
-          >
-            {{ spu.point }}积分
-            {{ !spu.pointPrice || spu.pointPrice === 0 ? '' : `+${fenToYuan(spu.pointPrice)}元` }}
-          </span>
-          <!-- 市场价 -->
-          <span
-            v-if="property.fields.marketPrice.show && spu.marketPrice"
-            :style="{ color: property.fields.marketPrice.color }"
-            class="ml-4px text-10px line-through"
-          >
-            ¥{{ fenToYuan(spu.marketPrice) }}
-          </span>
-        </div>
-        <div class="text-12px">
-          <!-- 销量 -->
-          <span
-            v-if="property.fields.salesCount.show"
-            :style="{ color: property.fields.salesCount.color }"
-          >
-            已兑{{ (spu.pointTotalStock || 0) - (spu.pointStock || 0) }}件
-          </span>
-          <!-- 库存 -->
-          <span v-if="property.fields.stock.show" :style="{ color: property.fields.stock.color }">
-            库存{{ spu.pointTotalStock || 0 }}
-          </span>
-        </div>
-      </div>
-      <!-- 购买按钮 -->
-      <div class="absolute bottom-8px right-8px">
-        <!-- 文字按钮 -->
-        <span
-          v-if="property.btnBuy.type === 'text'"
-          :style="{
-            background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`
-          }"
-          class="rounded-full p-x-12px p-y-4px text-12px text-white"
-        >
-          {{ property.btnBuy.text }}
-        </span>
-        <!-- 图片按钮 -->
-        <el-image
-          v-else
-          :src="property.btnBuy.imgUrl"
-          class="h-28px w-28px rounded-full"
-          fit="cover"
-        />
-      </div>
-    </div>
-  </div>
-</template>
-<script lang="ts" setup>
-import { PromotionPointProperty } from './config'
-import * as ProductSpuApi from '@/api/mall/product/spu'
-import { PointActivityApi, PointActivityVO, SpuExtension0 } from '@/api/mall/promotion/point'
-import { fenToYuan } from '@/utils'
-
-/** 积分商城卡片 */
-defineOptions({ name: 'PromotionPoint' })
-// 定义属性
-const props = defineProps<{ property: PromotionPointProperty }>()
-// 商品列表
-const spuList = ref<SpuExtension0[]>([])
-const spuIdList = ref<number[]>([])
-const pointActivityList = ref<PointActivityVO[]>([])
-
-watch(
-  () => props.property.activityIds,
-  async () => {
-    try {
-      // 新添加的积分商城组件,是没有活动ID的
-      const activityIds = props.property.activityIds
-      // 检查活动ID的有效性
-      if (Array.isArray(activityIds) && activityIds.length > 0) {
-        // 获取积分商城活动详情列表
-        pointActivityList.value = await PointActivityApi.getPointActivityListByIds(activityIds)
-
-        // 获取积分商城活动的 SPU 详情列表
-        spuList.value = []
-        spuIdList.value = pointActivityList.value.map((activity) => activity.spuId)
-        if (spuIdList.value.length > 0) {
-          spuList.value = await ProductSpuApi.getSpuDetailList(spuIdList.value)
-        }
-
-        // 更新 SPU 的最低兑换积分和所需兑换金额
-        pointActivityList.value.forEach((activity) => {
-          // 匹配spuId
-          const spu = spuList.value.find((spu) => spu.id === activity.spuId)
-          if (spu) {
-            spu.pointStock = activity.stock
-            spu.pointTotalStock = activity.totalStock
-            spu.point = activity.point
-            spu.pointPrice = activity.price
-          }
-        })
-      }
-    } catch (error) {
-      console.error('获取积分商城活动细节或 SPU 细节时出错:', error)
-    }
-  },
-  {
-    immediate: true,
-    deep: true
-  }
-)
-
-/**
- * 计算商品的间距
- * @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 calculateWidth = () => {
-  let width = '100%'
-  // 双列时每列的宽度为:(总宽度 - 间距)/ 2
-  if (props.property.layoutType === 'twoCol') {
-    width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`
-  }
-  return { width }
-}
-</script>
-
-<style lang="scss" scoped></style>

+ 0 - 154
src/components/DiyEditor/components/mobile/PromotionPoint/property.vue

@@ -1,154 +0,0 @@
-<template>
-  <ComponentContainerProperty v-model="formData.style">
-    <el-form :model="formData" label-width="80px">
-      <el-card class="property-group" header="积分商城活动" shadow="never">
-        <PointShowcase v-model="formData.activityIds" />
-      </el-card>
-      <el-card class="property-group" header="商品样式" 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 value="oneColBigImg">
-                <Icon icon="fluent:text-column-one-24-filled" />
-              </el-radio-button>
-            </el-tooltip>
-            <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">
-                <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.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">
-          <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-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 class="property-group" header="角标" shadow="never">
-        <el-form-item label="角标" prop="badge.show">
-          <el-switch v-model="formData.badge.show" />
-        </el-form-item>
-        <el-form-item v-if="formData.badge.show" label="角标" prop="badge.imgUrl">
-          <UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
-            <template #tip> 建议尺寸:36 * 22</template>
-          </UploadImg>
-        </el-form-item>
-      </el-card>
-      <el-card class="property-group" header="按钮" 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>
-          </el-form-item>
-        </template>
-      </el-card>
-      <el-card class="property-group" header="商品样式" shadow="never">
-        <el-form-item label="上圆角" prop="borderRadiusTop">
-          <el-slider
-            v-model="formData.borderRadiusTop"
-            :max="100"
-            :min="0"
-            :show-input-controls="false"
-            input-size="small"
-            show-input
-          />
-        </el-form-item>
-        <el-form-item label="下圆角" prop="borderRadiusBottom">
-          <el-slider
-            v-model="formData.borderRadiusBottom"
-            :max="100"
-            :min="0"
-            :show-input-controls="false"
-            input-size="small"
-            show-input
-          />
-        </el-form-item>
-        <el-form-item label="间隔" prop="space">
-          <el-slider
-            v-model="formData.space"
-            :max="100"
-            :min="0"
-            :show-input-controls="false"
-            input-size="small"
-            show-input
-          />
-        </el-form-item>
-      </el-card>
-    </el-form>
-  </ComponentContainerProperty>
-</template>
-
-<script lang="ts" setup>
-import { PromotionPointProperty } from './config'
-import { useVModel } from '@vueuse/core'
-import PointShowcase from '@/views/mall/promotion/point/components/PointShowcase.vue'
-
-// 秒杀属性面板
-defineOptions({ name: 'PromotionPointProperty' })
-
-const props = defineProps<{ modelValue: PromotionPointProperty }>()
-const emit = defineEmits(['update:modelValue'])
-const formData = useVModel(props, 'modelValue', emit)
-</script>
-
-<style lang="scss" scoped></style>

+ 0 - 96
src/components/DiyEditor/components/mobile/PromotionSeckill/config.ts

@@ -1,96 +0,0 @@
-import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
-
-/** 秒杀属性 */
-export interface PromotionSeckillProperty {
-  // 布局类型:单列 | 三列
-  layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'
-  // 商品字段
-  fields: {
-    // 商品名称
-    name: PromotionSeckillFieldProperty
-    // 商品简介
-    introduction: PromotionSeckillFieldProperty
-    // 商品价格
-    price: PromotionSeckillFieldProperty
-    // 市场价
-    marketPrice: PromotionSeckillFieldProperty
-    // 商品销量
-    salesCount: PromotionSeckillFieldProperty
-    // 商品库存
-    stock: PromotionSeckillFieldProperty
-  }
-  // 角标
-  badge: {
-    // 是否显示
-    show: boolean
-    // 角标图片
-    imgUrl: string
-  }
-  // 按钮
-  btnBuy: {
-    // 类型:文字 | 图片
-    type: 'text' | 'img'
-    // 文字
-    text: string
-    // 文字按钮:背景渐变起始颜色
-    bgBeginColor: string
-    // 文字按钮:背景渐变结束颜色
-    bgEndColor: string
-    // 图片按钮:图片地址
-    imgUrl: string
-  }
-  // 上圆角
-  borderRadiusTop: number
-  // 下圆角
-  borderRadiusBottom: number
-  // 间距
-  space: number
-  // 秒杀活动编号
-  activityIds: number[]
-  // 组件样式
-  style: ComponentStyle
-}
-
-// 商品字段
-export interface PromotionSeckillFieldProperty {
-  // 是否显示
-  show: boolean
-  // 颜色
-  color: string
-}
-
-// 定义组件
-export const component = {
-  id: 'PromotionSeckill',
-  name: '秒杀',
-  icon: 'mdi:calendar-time',
-  property: {
-    layoutType: 'oneColBigImg',
-    fields: {
-      name: { show: true, color: '#000' },
-      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: '' },
-    btnBuy: {
-      type: 'text',
-      text: '立即秒杀',
-      bgBeginColor: '#FF6000',
-      bgEndColor: '#FE832A',
-      imgUrl: ''
-    },
-    borderRadiusTop: 8,
-    borderRadiusBottom: 8,
-    space: 8,
-    style: {
-      bgType: 'color',
-      bgColor: '',
-      marginLeft: 8,
-      marginRight: 8,
-      marginBottom: 8
-    } as ComponentStyle
-  }
-} as DiyComponent<PromotionSeckillProperty>

+ 0 - 201
src/components/DiyEditor/components/mobile/PromotionSeckill/index.vue

@@ -1,201 +0,0 @@
-<template>
-  <div :class="`box-content min-h-30px w-full flex flex-row flex-wrap`" ref="containerRef">
-    <div
-      class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
-      :style="{
-        ...calculateSpace(index),
-        ...calculateWidth(),
-        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>
-      <!-- 商品封面图 -->
-      <div
-        :class="[
-          'h-140px',
-          {
-            'w-full': property.layoutType !== 'oneColSmallImg',
-            'w-140px': property.layoutType === 'oneColSmallImg'
-          }
-        ]"
-      >
-        <el-image fit="cover" class="h-full w-full" :src="spu.picUrl" />
-      </div>
-      <div
-        :class="[
-          ' flex flex-col gap-8px p-8px box-border',
-          {
-            'w-full': property.layoutType !== 'oneColSmallImg',
-            'w-[calc(100%-140px-16px)]': property.layoutType === 'oneColSmallImg'
-          }
-        ]"
-      >
-        <!-- 商品名称 -->
-        <div
-          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 }"
-        >
-          {{ spu.name }}
-        </div>
-        <!-- 商品简介 -->
-        <div
-          v-if="property.fields.introduction.show"
-          class="truncate text-12px"
-          :style="{ color: property.fields.introduction.color }"
-        >
-          {{ spu.introduction }}
-        </div>
-        <div>
-          <!-- 价格 -->
-          <span
-            v-if="property.fields.price.show"
-            class="text-16px"
-            :style="{ color: property.fields.price.color }"
-          >
-            ¥{{ 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>
-        </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>
-</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'
-import { fenToYuan } from '@/utils'
-
-/** 秒杀卡片 */
-defineOptions({ name: 'PromotionSeckill' })
-// 定义属性
-const props = defineProps<{ property: PromotionSeckillProperty }>()
-// 商品列表
-const spuList = ref<ProductSpuApi.Spu[]>([])
-const spuIdList = ref<number[]>([])
-const seckillActivityList = ref<SeckillActivityApi.SeckillActivityVO[]>([])
-
-watch(
-  () => props.property.activityIds,
-  async () => {
-    try {
-      // 新添加的秒杀组件,是没有活动ID的
-      const activityIds = props.property.activityIds
-      // 检查活动ID的有效性
-      if (Array.isArray(activityIds) && activityIds.length > 0) {
-        // 获取秒杀活动详情列表
-        seckillActivityList.value =
-          await SeckillActivityApi.getSeckillActivityListByIds(activityIds)
-
-        // 获取秒杀活动的 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,
-    deep: true
-  }
-)
-
-/**
- * 计算商品的间距
- * @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 calculateWidth = () => {
-  let width = '100%'
-  // 双列时每列的宽度为:(总宽度 - 间距)/ 2
-  if (props.property.layoutType === 'twoCol') {
-    width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`
-  }
-  return { width }
-}
-</script>
-
-<style scoped lang="scss"></style>

+ 0 - 164
src/components/DiyEditor/components/mobile/PromotionSeckill/property.vue

@@ -1,164 +0,0 @@
-<template>
-  <ComponentContainerProperty v-model="formData.style">
-    <el-form label-width="80px" :model="formData">
-      <el-card header="秒杀活动" class="property-group" shadow="never">
-        <SeckillShowcase v-model="formData.activityIds" />
-      </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 value="oneColBigImg">
-                <Icon icon="fluent:text-column-one-24-filled" />
-              </el-radio-button>
-            </el-tooltip>
-            <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">
-                <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.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">
-          <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-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 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="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>
-          </el-form-item>
-        </template>
-      </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 { useVModel } from '@vueuse/core'
-import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
-import { CommonStatusEnum } from '@/utils/constants'
-import SeckillShowcase from '@/views/mall/promotion/seckill/components/SeckillShowcase.vue'
-
-// 秒杀属性面板
-defineOptions({ name: 'PromotionSeckillProperty' })
-
-const props = defineProps<{ modelValue: PromotionSeckillProperty }>()
-const emit = defineEmits(['update:modelValue'])
-const formData = useVModel(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>

+ 1 - 24
src/router/modules/remaining.ts

@@ -387,30 +387,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
     name: 'DiyCenter',
     meta: { hidden: true },
     component: Layout,
-    children: [
-      {
-        path: 'template/decorate/:id',
-        name: 'DiyTemplateDecorate',
-        meta: {
-          title: '模板装修',
-          noCache: false,
-          hidden: true,
-          activeMenu: '/mall/promotion/diy-template/diy-template'
-        },
-        component: () => import('@/views/mall/promotion/diy/template/decorate.vue')
-      },
-      {
-        path: 'page/decorate/:id',
-        name: 'DiyPageDecorate',
-        meta: {
-          title: '页面装修',
-          noCache: false,
-          hidden: true,
-          activeMenu: '/mall/promotion/diy-template/diy-page'
-        },
-        component: () => import('@/views/mall/promotion/diy/page/decorate.vue')
-      }
-    ]
+    children: []
   },
   {
     path: '/ai',

+ 10 - 10
src/views/member/user/index.vue

@@ -199,7 +199,7 @@
   <!-- 修改用户余额弹窗 -->
   <UserBalanceUpdateForm ref="UpdateBalanceFormRef" @success="getList" />
   <!-- 发送优惠券弹窗 -->
-  <CouponSendForm ref="couponSendFormRef" />
+  <!-- <CouponSendForm ref="couponSendFormRef" /> -->
 </template>
 <script lang="ts" setup>
 import { dateFormatter } from '@/utils/formatTime'
@@ -212,7 +212,7 @@ import MemberGroupSelect from '@/views/member/group/components/MemberGroupSelect
 import UserLevelUpdateForm from './components/UserLevelUpdateForm.vue'
 import UserPointUpdateForm from './components/UserPointUpdateForm.vue'
 import UserBalanceUpdateForm from './components/UserBalanceUpdateForm.vue'
-import { CouponSendForm } from '@/views/mall/promotion/coupon/components'
+// import { CouponSendForm } from '@/views/mall/promotion/coupon/components'
 import { checkPermi } from '@/utils/permission'
 
 defineOptions({ name: 'MemberUser' })
@@ -281,14 +281,14 @@ const handleSelectionChange = (rows: UserApi.UserVO[]) => {
 }
 
 /** 发送优惠券 */
-const couponSendFormRef = ref()
-const openCoupon = () => {
-  if (selectedIds.value.length === 0) {
-    message.warning('请选择要发送优惠券的用户')
-    return
-  }
-  couponSendFormRef.value.open(selectedIds.value)
-}
+// const couponSendFormRef = ref()
+// const openCoupon = () => {
+//   if (selectedIds.value.length === 0) {
+//     message.warning('请选择要发送优惠券的用户')
+//     return
+//   }
+//   couponSendFormRef.value.open(selectedIds.value)
+// }
 
 /** 操作分发 */
 const handleCommand = (command: string, row: UserApi.UserVO) => {

+ 504 - 121
src/views/tenantManage/index.vue

@@ -1,187 +1,570 @@
 <template>
-  <div class="tenant-manage-container">
-    <!-- 新增租户表单 -->
-    <el-form
-      ref="tenantFormRef"
-      :model="tenantForm"
-      :rules="tenantRules"
-      label-width="120px"
-      class="tenant-form"
+  <el-container class="tenant-manage-container">
+    <el-row :gutter="20" class="layout-wrapper">
+      <!-- 左侧:租户树状结构 -->
+      <el-col :xs="24" :sm="24" :md="6" :lg="6" class="left-panel-col">
+        <el-card class="left-panel" shadow="hover">
+          <template #header>
+            <div class="card-header">
+              <span>租户列表</span>
+              <el-button type="primary" size="small" @click="openAddTenantDialog">
+                新增租户
+              </el-button>
+            </div>
+          </template>
+          <el-tree
+            ref="treeRef"
+            :data="tenantTreeData"
+            node-key="id"
+            :props="{ children: 'children', label: 'label' }"
+            :expand-on-click-node="false"
+            @node-click="handleNodeClick"
+            highlight-current
+            class="tree-container"
+          />
+        </el-card>
+      </el-col>
+
+      <!-- 右侧:用户表格 -->
+      <el-col :xs="24" :sm="24" :md="18" :lg="18" class="right-panel-col">
+        <el-card v-if="selectedTenant" class="right-panel" shadow="hover">
+          <template #header>
+            <div class="card-header">
+              <span>{{ selectedTenant.name }} - 用户列表</span>
+              <el-button type="primary" size="small" @click="openAddUserDialog">
+                新增用户
+              </el-button>
+            </div>
+          </template>
+          <el-table :data="userTableData" stripe border class="user-table" max-height="600">
+            <el-table-column prop="id" label="ID" width="100" />
+            <el-table-column prop="username" label="用户名" width="150" />
+            <el-table-column prop="email" label="邮箱" min-width="200" />
+            <el-table-column prop="status" label="状态" width="100">
+              <template #default="{ row }">
+                <el-tag :type="row.status === 'active' ? 'success' : 'info'">
+                  {{ row.status === 'active' ? '活跃' : '禁用' }}
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="200" fixed="right" align="center">
+              <template #default="{ row }">
+                <el-space>
+                  <el-button type="primary" size="small" link @click="handleEditUser(row)">
+                    编辑
+                  </el-button>
+                  <el-button type="danger" size="small" link @click="handleDeleteUser(row)">
+                    删除
+                  </el-button>
+                </el-space>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-card>
+        <el-empty v-else description="请选择左侧租户查看用户列表" />
+      </el-col>
+    </el-row>
+
+    <!-- 新增/编辑租户对话框 -->
+    <el-dialog
+      v-model="tenantDialogVisible"
+      :title="isEditingTenant ? '编辑租户' : '新增租户'"
+      width="500px"
+      @close="resetTenantForm"
     >
-      <el-form-item label="机构名" prop="name">
-        <el-input v-model="tenantForm.name" placeholder="请输入机构名" />
-      </el-form-item>
-      <el-form-item label="机构简介" prop="introduction">
-        <el-input
-          v-model="tenantForm.introduction"
-          type="textarea"
-          :maxlength="512"
-          show-word-limit
-          placeholder="请输入机构简介(512字内)"
-        />
-      </el-form-item>
-      <el-form-item label="机构地址" prop="address">
-        <el-input v-model="tenantForm.address" placeholder="请输入机构地址" />
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" @click="submitTenantForm">新增租户</el-button>
-      </el-form-item>
-    </el-form>
-
-    <!-- 新增用户表单(显示在新增租户成功后) -->
-    <el-form
-      v-if="showUserForm"
-      ref="userFormRef"
-      :model="userForm"
-      :rules="userRules"
-      label-width="120px"
-      class="user-form"
+      <el-form ref="tenantFormRef" :model="tenantForm" :rules="tenantRules" label-width="100px">
+        <el-form-item label="机构名" prop="name">
+          <el-input v-model="tenantForm.name" placeholder="请输入机构名" clearable />
+        </el-form-item>
+        <el-form-item label="机构简介" prop="introduction">
+          <el-input
+            v-model="tenantForm.introduction"
+            type="textarea"
+            :maxlength="512"
+            show-word-limit
+            placeholder="请输入机构简介(512字内)"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="机构地址" prop="address">
+          <el-input v-model="tenantForm.address" placeholder="请输入机构地址" clearable />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-space>
+          <el-button @click="tenantDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="submitTenantForm">确定</el-button>
+        </el-space>
+      </template>
+    </el-dialog>
+
+    <!-- 新增/编辑用户对话框 -->
+    <el-dialog
+      v-model="userDialogVisible"
+      :title="isEditingUser ? '编辑用户' : '新增用户'"
+      width="500px"
+      @close="resetUserForm"
     >
-      <el-form-item label="用户名" prop="username">
-        <el-input v-model="userForm.username" placeholder="请输入用户名" />
-      </el-form-item>
-      <el-form-item label="密码" prop="password">
-        <el-input
-          v-model="userForm.password"
-          type="password"
-          placeholder="请输入密码"
-          show-password
-        />
-      </el-form-item>
-      <el-form-item label="机构ID" prop="organizationId">
-        <el-input
-          v-model="userForm.organizationId"
-          placeholder="新增租户后会生成机构ID"
-          disabled
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" @click="submitUserForm">新增用户</el-button>
-      </el-form-item>
-    </el-form>
-  </div>
+      <el-form ref="userFormRef" :model="userForm" :rules="userRules" label-width="100px">
+        <el-form-item label="用户名" prop="username">
+          <el-input v-model="userForm.username" placeholder="请输入用户名" clearable />
+        </el-form-item>
+        <el-form-item label="邮箱" prop="email">
+          <el-input v-model="userForm.email" placeholder="请输入邮箱" clearable />
+        </el-form-item>
+        <el-form-item v-if="!isEditingUser" label="密码" prop="password">
+          <el-input
+            v-model="userForm.password"
+            type="password"
+            placeholder="请输入密码"
+            show-password
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select v-model="userForm.status" placeholder="请选择状态" clearable>
+            <el-option label="活跃" value="active" />
+            <el-option label="禁用" value="disabled" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-space>
+          <el-button @click="userDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="submitUserForm">确定</el-button>
+        </el-space>
+      </template>
+    </el-dialog>
+  </el-container>
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive } from "vue";
-import type { FormInstance, FormRules } from "element-plus";
-import { ElMessage } from "element-plus";
+import { ref, reactive } from 'vue'
+import type { FormInstance, FormRules } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
 
 // 表单引用
-const tenantFormRef = ref<FormInstance>();
-const userFormRef = ref<FormInstance>();
+const tenantFormRef = ref<FormInstance>()
+const userFormRef = ref<FormInstance>()
+const treeRef = ref()
+
+// 租户数据接口
+interface Tenant {
+  id: string
+  name: string
+  introduction: string
+  address: string
+  children?: Tenant[]
+}
+
+// 树节点数据接口
+interface TreeNode {
+  id: string
+  label: string
+  children?: TreeNode[]
+  data?: Tenant
+}
+
+// 用户数据接口
+interface User {
+  id: string
+  username: string
+  email: string
+  password?: string
+  status: 'active' | 'disabled'
+  tenantId: string
+}
 
 // 租户表单数据
 interface TenantForm {
-  name: string;
-  introduction: string;
-  address: string;
+  id?: string
+  name: string
+  introduction: string
+  address: string
 }
 
 // 用户表单数据
 interface UserForm {
-  username: string;
-  password: string;
-  organizationId: string;
+  id?: string
+  username: string
+  email: string
+  password: string
+  status: 'active' | 'disabled'
+  tenantId: string
 }
 
+// 租户树数据
+const tenantTreeData = ref<TreeNode[]>([
+  {
+    id: 'tenant-1',
+    label: 'A公司',
+    data: {
+      id: 'tenant-1',
+      name: 'A公司',
+      introduction: '这是A公司的介绍',
+      address: '北京市朝阳区'
+    }
+  },
+  {
+    id: 'tenant-2',
+    label: 'B公司',
+    data: {
+      id: 'tenant-2',
+      name: 'B公司',
+      introduction: '这是B公司的介绍',
+      address: '上海市浦东新区'
+    }
+  }
+])
+
+// 用户表格数据
+const userTableData = ref<User[]>([
+  {
+    id: 'user-1',
+    username: 'admin',
+    email: 'admin@example.com',
+    status: 'active',
+    tenantId: 'tenant-1'
+  },
+  {
+    id: 'user-2',
+    username: 'user1',
+    email: 'user1@example.com',
+    status: 'active',
+    tenantId: 'tenant-1'
+  }
+])
+
+// 选中的租户
+const selectedTenant = ref<Tenant | null>(null)
+
+// 对话框显示状态
+const tenantDialogVisible = ref(false)
+const userDialogVisible = ref(false)
+
+// 编辑状态
+const isEditingTenant = ref(false)
+const isEditingUser = ref(false)
+
 // 租户表单数据和验证规则
 const tenantForm = reactive<TenantForm>({
-  name: "",
-  introduction: "",
-  address: "",
-});
+  name: '',
+  introduction: '',
+  address: ''
+})
 
 const tenantRules = reactive<FormRules>({
   name: [
-    { required: true, message: "请输入机构名", trigger: "blur" },
-    { min: 2, max: 20, message: "长度在 2 到 20 个字符", trigger: "blur" },
+    { required: true, message: '请输入机构名', trigger: 'blur' },
+    { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
   ],
   introduction: [
-    { required: true, message: "请输入机构简介", trigger: "blur" },
-    { max: 512, message: "机构简介不能超过512个字", trigger: "blur" },
+    { required: true, message: '请输入机构简介', trigger: 'blur' },
+    { max: 512, message: '机构简介不能超过512个字', trigger: 'blur' }
   ],
   address: [
-    { required: true, message: "请输入机构地址", trigger: "blur" },
-    { min: 5, max: 100, message: "长度在 5 到 100 个字符", trigger: "blur" },
-  ],
-});
+    { required: true, message: '请输入机构地址', trigger: 'blur' },
+    { min: 5, max: 100, message: '长度在 5 到 100 个字符', trigger: 'blur' }
+  ]
+})
 
 // 用户表单数据和验证规则
 const userForm = reactive<UserForm>({
-  username: "",
-  password: "",
-  organizationId: "",
-});
+  username: '',
+  email: '',
+  password: '',
+  status: 'active',
+  tenantId: ''
+})
 
 const userRules = reactive<FormRules>({
   username: [
-    { required: true, message: "请输入用户名", trigger: "blur" },
-    { min: 3, max: 20, message: "长度在 3 到 20 个字符", trigger: "blur" },
+    { required: true, message: '请输入用户名', trigger: 'blur' },
+    { min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
+  ],
+  email: [
+    { required: true, message: '请输入邮箱', trigger: 'blur' },
+    { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
   ],
   password: [
-    { required: true, message: "请输入密码", trigger: "blur" },
-    { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" },
+    { required: true, message: '请输入密码', trigger: 'blur' },
+    { min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
   ],
-  organizationId: [{ required: true, message: "请输入机构ID", trigger: "blur" }],
-});
+  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
+})
+
+// 模拟生成ID
+const generateId = (prefix: string): string => {
+  return prefix + '-' + Math.random().toString(36).substr(2, 9)
+}
+
+// 处理树节点点击
+const handleNodeClick = (data: TreeNode) => {
+  selectedTenant.value = data.data || null
+  // 根据选中的租户ID过滤用户列表
+  if (selectedTenant.value) {
+    userTableData.value = userTableData.value.filter(
+      (user) => user.tenantId === selectedTenant.value!.id
+    )
+  }
+}
+
+// 打开新增租户对话框
+const openAddTenantDialog = () => {
+  isEditingTenant.value = false
+  resetTenantForm()
+  tenantDialogVisible.value = true
+}
 
-// 控制用户表单的显示
-const showUserForm = ref(false);
+// 打开新增用户对话框
+const openAddUserDialog = () => {
+  if (!selectedTenant.value) {
+    ElMessage.warning('请先选择一个租户')
+    return
+  }
+  isEditingUser.value = false
+  resetUserForm()
+  userForm.tenantId = selectedTenant.value.id
+  userDialogVisible.value = true
+}
 
-// 模拟生成机构ID
-const generateOrgId = (): string => {
-  return "ORG_" + Math.random().toString(36).substr(2, 9).toUpperCase();
-};
+// 重置租户表单
+const resetTenantForm = () => {
+  tenantForm.id = undefined
+  tenantForm.name = ''
+  tenantForm.introduction = ''
+  tenantForm.address = ''
+  tenantFormRef.value?.clearValidate()
+}
+
+// 重置用户表单
+const resetUserForm = () => {
+  userForm.id = undefined
+  userForm.username = ''
+  userForm.email = ''
+  userForm.password = ''
+  userForm.status = 'active'
+  userForm.tenantId = ''
+  userFormRef.value?.clearValidate()
+}
 
 // 提交租户表单
 const submitTenantForm = async () => {
-  if (!tenantFormRef.value) return;
+  if (!tenantFormRef.value) return
   try {
     await tenantFormRef.value.validate((valid) => {
       if (valid) {
-        // 这里应该是API调用
-        console.log("提交租户表单:", tenantForm);
-        userForm.organizationId = generateOrgId();
-        showUserForm.value = true;
-        ElMessage.success("租户创建成功!");
+        if (isEditingTenant.value) {
+          // 编辑租户
+          const index = tenantTreeData.value.findIndex((t) => t.id === tenantForm.id)
+          if (index > -1) {
+            tenantTreeData.value[index].label = tenantForm.name
+            tenantTreeData.value[index].data = {
+              id: tenantForm.id!,
+              name: tenantForm.name,
+              introduction: tenantForm.introduction,
+              address: tenantForm.address
+            }
+          }
+          ElMessage.success('租户编辑成功!')
+        } else {
+          // 新增租户
+          const newId = generateId('tenant')
+          const newTenant: TreeNode = {
+            id: newId,
+            label: tenantForm.name,
+            data: {
+              id: newId,
+              name: tenantForm.name,
+              introduction: tenantForm.introduction,
+              address: tenantForm.address
+            }
+          }
+          tenantTreeData.value.push(newTenant)
+          ElMessage.success('租户创建成功!')
+        }
+        tenantDialogVisible.value = false
+        resetTenantForm()
       }
-    });
+    })
   } catch (error) {
-    console.error("租户表单验证失败:", error);
-    ElMessage.error("请检查租户表单输入");
+    console.error('租户表单验证失败:', error)
+    ElMessage.error('请检查租户表单输入')
   }
-};
+}
+
+// 处理编辑用户
+const handleEditUser = (row: User) => {
+  isEditingUser.value = true
+  userForm.id = row.id
+  userForm.username = row.username
+  userForm.email = row.email
+  userForm.status = row.status
+  userForm.tenantId = row.tenantId
+  userForm.password = ''
+  userDialogVisible.value = true
+}
+
+// 处理删除用户
+const handleDeleteUser = (row: User) => {
+  ElMessageBox.confirm('确定删除该用户吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(() => {
+      const index = userTableData.value.findIndex((u) => u.id === row.id)
+      if (index > -1) {
+        userTableData.value.splice(index, 1)
+      }
+      ElMessage.success('用户删除成功!')
+    })
+    .catch(() => {
+      ElMessage.info('已取消删除')
+    })
+}
 
 // 提交用户表单
 const submitUserForm = async () => {
-  if (!userFormRef.value) return;
+  if (!userFormRef.value) return
   try {
     await userFormRef.value.validate((valid) => {
       if (valid) {
-        // 这里应该是API调用
-        console.log("提交用户表单:", userForm);
-        ElMessage.success("用户创建成功!");
+        if (isEditingUser.value) {
+          // 编辑用户
+          const index = userTableData.value.findIndex((u) => u.id === userForm.id)
+          if (index > -1) {
+            userTableData.value[index] = {
+              id: userForm.id!,
+              username: userForm.username,
+              email: userForm.email,
+              status: userForm.status,
+              tenantId: userForm.tenantId
+            }
+          }
+          ElMessage.success('用户编辑成功!')
+        } else {
+          // 新增用户
+          const newUser: User = {
+            id: generateId('user'),
+            username: userForm.username,
+            email: userForm.email,
+            status: userForm.status,
+            tenantId: userForm.tenantId
+          }
+          userTableData.value.push(newUser)
+          ElMessage.success('用户创建成功!')
+        }
+        userDialogVisible.value = false
+        resetUserForm()
       }
-    });
+    })
   } catch (error) {
-    console.error("用户表单验证失败:", error);
-    ElMessage.error("请检查用户表单输入");
+    console.error('用户表单验证失败:', error)
+    ElMessage.error('请检查用户表单输入')
   }
-};
+}
 </script>
 
 <style scoped>
 .tenant-manage-container {
   padding: 20px;
+  height: 100%;
+  width: 100%;
+}
+
+.layout-wrapper {
+  width: 100%;
+}
+
+.left-panel-col {
+  margin-bottom: 20px;
+}
+
+.left-panel {
+  height: 100%;
+  min-height: 500px;
+}
+
+.right-panel {
+  height: 100%;
+  min-height: 500px;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+}
+
+.card-header span {
+  font-weight: 600;
+  font-size: 14px;
+}
+
+.tree-container {
+  max-height: 600px;
+  overflow-y: auto;
+}
+
+.user-table {
+  width: 100%;
+}
+
+/* Element Plus 组件样式调整 */
+:deep(.el-card__header) {
+  padding: 15px 20px;
+  border-bottom: 1px solid #ebeef5;
+}
+
+:deep(.el-card__body) {
+  padding: 20px;
+}
+
+:deep(.el-tree) {
+  background-color: transparent;
+}
+
+:deep(.el-tree-node__content) {
+  height: 32px;
 }
 
-.tenant-form {
-  max-width: 600px;
-  margin-bottom: 40px;
+:deep(.el-table) {
+  font-size: 13px;
 }
 
-.user-form {
-  max-width: 600px;
+:deep(.el-table__body-wrapper) {
+  max-height: 600px;
+  overflow-y: auto;
+}
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+  .left-panel {
+    min-height: 400px;
+  }
+
+  .right-panel {
+    min-height: 400px;
+  }
+}
+
+@media (max-width: 768px) {
+  .tenant-manage-container {
+    padding: 10px;
+  }
+
+  .left-panel {
+    min-height: 300px;
+  }
+
+  .right-panel {
+    min-height: 400px;
+  }
+
+  .left-panel-col {
+    margin-bottom: 20px;
+  }
 }
 </style>