Răsfoiți Sursa

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

YunaiV 1 an în urmă
părinte
comite
c3b210d2b5
35 a modificat fișierele cu 804 adăugiri și 144 ștergeri
  1. 1 1
      src/api/mall/promotion/kefu/conversation/index.ts
  2. 0 0
      src/assets/svgs/audit1.svg
  3. 0 0
      src/assets/svgs/audit2.svg
  4. 0 0
      src/assets/svgs/audit3.svg
  5. 1 0
      src/assets/svgs/send.svg
  6. 1 0
      src/components/DiyEditor/components/ComponentContainer.vue
  7. 2 2
      src/components/Draggable/index.vue
  8. 24 9
      src/components/IFrame/src/IFrame.vue
  9. 1 1
      src/layout/components/Footer/src/Footer.vue
  10. 10 0
      src/layout/components/Menu/src/Menu.vue
  11. 3 3
      src/layout/components/TabMenu/src/TabMenu.vue
  12. 10 8
      src/layout/components/TagsView/src/TagsView.vue
  13. 13 25
      src/layout/components/useRenderLayout.tsx
  14. 8 8
      src/utils/formatTime.ts
  15. 588 0
      src/views/bpm/processInstance/detail/index_new.vue
  16. 2 2
      src/views/infra/druid/index.vue
  17. 1 1
      src/views/infra/server/index.vue
  18. 1 1
      src/views/infra/skywalking/index.vue
  19. 2 2
      src/views/infra/swagger/index.vue
  20. 27 0
      src/views/mall/promotion/components/SpuAndSkuList.vue
  21. 46 9
      src/views/mall/promotion/discountActivity/DiscountActivityForm.vue
  22. 0 11
      src/views/mall/promotion/discountActivity/discountActivity.data.ts
  23. 11 11
      src/views/mall/promotion/discountActivity/index.vue
  24. 7 6
      src/views/mall/promotion/kefu/components/KeFuMessageList.vue
  25. 1 0
      src/views/mall/promotion/kefu/components/history/ProductBrowsingHistory.vue
  26. 11 2
      src/views/mall/promotion/kefu/components/message/OrderItem.vue
  27. 16 3
      src/views/mall/promotion/kefu/components/message/ProductItem.vue
  28. 2 2
      src/views/mall/trade/afterSale/detail/index.vue
  29. 2 13
      src/views/mall/trade/delivery/pickUpStore/PickUpStoreForm.vue
  30. 3 1
      src/views/report/goview/index.vue
  31. 4 3
      src/views/report/jmreport/index.vue
  32. 1 8
      src/views/system/role/RoleAssignMenuForm.vue
  33. 3 3
      src/views/system/role/RoleDataPermissionForm.vue
  34. 1 8
      src/views/system/tenantPackage/TenantPackageForm.vue
  35. 1 1
      uno.config.ts

+ 1 - 1
src/api/mall/promotion/kefu/conversation/index.ts

@@ -30,6 +30,6 @@ export const KeFuConversationApi = {
   },
   // 删除客服会话
   deleteConversation: async (id: number) => {
-    return await request.get({ url: '/promotion/kefu-conversation/delete?id' + id })
+    return await request.delete({ url: `/promotion/kefu-conversation/delete?id=${id}`})
   }
 }

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
src/assets/svgs/audit1.svg


Fișier diff suprimat deoarece este prea mare
+ 0 - 0
src/assets/svgs/audit2.svg


Fișier diff suprimat deoarece este prea mare
+ 0 - 0
src/assets/svgs/audit3.svg


+ 1 - 0
src/assets/svgs/send.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1724297262365" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1396" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M707.91 103c16.28 0 29.522 13.007 29.897 29.195l0.009 0.706v111.878a29.96 29.96 0 0 1-0.898 7.3l171.177-0.001c16.28 0 29.522 13.007 29.897 29.195l0.008 0.706v637.12c0 16.278-13.01 29.518-29.2 29.893l-0.705 0.008H270.884c-16.28 0-29.522-13.007-29.897-29.195l-0.008-0.706V787.274c0-16.514 13.389-29.9 29.905-29.9 16.28 0 29.522 13.007 29.897 29.194l0.008 0.706v101.924h577.4V311.88h-577.4v88.787c0 16.278-13.009 29.518-29.2 29.893l-0.705 0.008c-16.28 0-29.522-13.008-29.897-29.195l-0.008-0.706V281.979c0-16.278 13.009-29.518 29.2-29.893l0.705-0.008h408.019a29.916 29.916 0 0 1-0.89-6.593l-0.008-0.706v-81.978H132.808v407.113h385.787L408.223 456.982c-11.36-11.624-11.329-30.143-0.066-41.729l0.554-0.555c11.625-11.358 30.147-11.327 41.734-0.066l0.555 0.554 161.028 164.762c11.244 11.504 11.344 29.793 0.362 41.42l-0.55 0.565-161.027 161.849c-11.648 11.707-30.583 11.757-42.292 0.11-11.524-11.461-11.754-29.979-0.657-41.723l0.546-0.563 111.319-111.89H102.905c-16.28 0-29.522-13.007-29.897-29.195l-0.008-0.705V132.9c0-16.278 13.01-29.518 29.2-29.893l0.705-0.008H707.91z" p-id="1397"></path></svg>

+ 1 - 0
src/components/DiyEditor/components/ComponentContainer.vue

@@ -165,6 +165,7 @@ $toolbar-position: -55px;
       width: 80px;
       height: 25px;
       font-size: 12px;
+      color: #6a6a6a;
       line-height: 25px;
       text-align: center;
       background: #fff;

+ 2 - 2
src/components/Draggable/index.vue

@@ -13,9 +13,9 @@
         class="mb-4px flex flex-col gap-4px border border-gray-2 border-rounded rounded border-solid p-8px"
       >
         <!-- 操作按钮区 -->
-        <div class="m--8px m-b-4px flex flex-row items-center justify-between bg-gray-1 p-8px">
+        <div class="m--8px m-b-4px flex flex-row items-center justify-between p-8px" style="background-color: var(--app-content-bg-color);">
           <el-tooltip content="拖动排序">
-            <Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
+            <Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" style="color: #8a909c;" />
           </el-tooltip>
           <el-tooltip content="删除">
             <Icon

+ 24 - 9
src/components/IFrame/src/IFrame.vue

@@ -7,26 +7,41 @@ const props = defineProps({
   src: propTypes.string.def('')
 })
 const loading = ref(true)
-const height = ref('')
 const frameRef = ref<HTMLElement | null>(null)
 const init = () => {
-  height.value = document.documentElement.clientHeight - 94.5 + 'px'
-  loading.value = false
+  nextTick(() => {
+    loading.value = true
+    if (!frameRef.value) return
+    frameRef.value.onload = () => {
+      loading.value = false
+    }
+  })
 }
 onMounted(() => {
-  setTimeout(() => {
-    init()
-  }, 300)
+  init()
 })
+watch(
+  () => props.src,
+  () => {
+    init()
+  }
+)
 </script>
 <template>
-  <div v-loading="loading" :style="'height:' + height">
+  <div
+    v-loading="loading"
+    class="w-full h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-2px)]"
+  >
     <iframe
       ref="frameRef"
       :src="props.src"
-      frameborder="no"
+      frameborder="0"
       scrolling="auto"
-      style="width: 100%; height: 100%"
+      height="100%"
+      width="100%"
+      allowfullscreen="true"
+      webkitallowfullscreen="true"
+      mozallowfullscreen="true"
     ></iframe>
   </div>
 </template>

+ 1 - 1
src/layout/components/Footer/src/Footer.vue

@@ -17,7 +17,7 @@ const title = computed(() => appStore.getTitle)
 <template>
   <div
     :class="prefixCls"
-    class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)]"
+    class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)] overflow-hidden"
   >
     <span class="text-14px">Copyright ©2022-{{ title }}</span>
   </div>

+ 10 - 0
src/layout/components/Menu/src/Menu.vue

@@ -195,6 +195,16 @@ $prefix-cls: #{$namespace}-menu;
     }
   }
 
+  // 垂直菜单
+  &__vertical {
+    :deep(.#{$elNamespace}-menu--vertical) {
+      &:not(.#{$elNamespace}-menu--collapse) .#{$elNamespace}-sub-menu__title,
+      .#{$elNamespace}-menu-item {
+        padding-right: 0;
+      }
+    }
+  }
+
   // 水平菜单
   &__horizontal {
     height: calc(var(--top-tool-height)) !important;

+ 3 - 3
src/layout/components/TabMenu/src/TabMenu.vue

@@ -139,7 +139,7 @@ export default defineComponent({
         id={`${variables.namespace}-menu`}
         class={[
           prefixCls,
-          'relative bg-[var(--left-menu-bg-color)] top-1px layout-border__right',
+          'relative bg-[var(--left-menu-bg-color)] layout-border__right',
           {
             'w-[var(--tab-menu-max-width)]': !unref(collapse),
             'w-[var(--tab-menu-min-width)]': unref(collapse)
@@ -147,7 +147,7 @@ export default defineComponent({
         ]}
         onMouseleave={mouseleave}
       >
-        <ElScrollbar class="!h-[calc(100%-var(--tab-menu-collapse-height)-1px)]">
+        <ElScrollbar class="!h-[calc(100%-var(--tab-menu-collapse-height))]">
           <div>
             {() => {
               return unref(tabRouters).map((v) => {
@@ -199,7 +199,7 @@ export default defineComponent({
             {
               '!left-[var(--tab-menu-min-width)]': unref(collapse),
               '!left-[var(--tab-menu-max-width)]': !unref(collapse),
-              '!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu) || unref(fixedMenu),
+              '!w-[var(--left-menu-max-width)]': unref(showMenu) || unref(fixedMenu),
               '!w-0': !unref(showMenu) && !unref(fixedMenu)
             }
           ]}

+ 10 - 8
src/layout/components/TagsView/src/TagsView.vue

@@ -280,7 +280,7 @@ watch(
     </span>
     <div class="flex-1 overflow-hidden">
       <ElScrollbar ref="scrollbarRef" class="h-full" @scroll="scroll">
-        <div class="h-full flex">
+        <div class="h-[var(--tags-view-height)] flex">
           <ContextMenu
             v-for="item in visitedViews"
             :key="item.fullPath"
@@ -491,10 +491,10 @@ $prefix-cls: #{$namespace}-tags-view;
 
     &::before {
       position: absolute;
-      top: 1px;
+      top: 0;
       left: 0;
       width: 100%;
-      height: calc(100% - 1px);
+      height: 100%;
       border-left: 1px solid var(--el-border-color);
       content: '';
     }
@@ -502,10 +502,10 @@ $prefix-cls: #{$namespace}-tags-view;
     &--first {
       &::before {
         position: absolute;
-        top: 1px;
+        top: 0;
         left: 0;
         width: 100%;
-        height: calc(100% - 1px);
+        height: 100%;
         border-right: 1px solid var(--el-border-color);
         border-left: none;
         content: '';
@@ -515,7 +515,7 @@ $prefix-cls: #{$namespace}-tags-view;
 
   &__item {
     position: relative;
-    top: 2px;
+    top: 3px;
     height: calc(100% - 6px);
     padding-right: 15px;
     margin-left: 4px;
@@ -523,6 +523,7 @@ $prefix-cls: #{$namespace}-tags-view;
     cursor: pointer;
     border: 1px solid #d9d9d9;
     border-radius: 2px;
+    box-sizing: border-box;
 
     &--close {
       position: absolute;
@@ -562,10 +563,11 @@ $prefix-cls: #{$namespace}-tags-view;
   }
 
   &__item--immerse {
-    top: 3px;
+    top: 2px;
+    height: calc(100% - 3px);
     padding-right: 35px;
     margin: 0 -10px;
-    border: 1px solid transparent;
+    border: none !important;
     -webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='68' height='34' viewBox='0 0 68 34' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m27,0c-7.99582,0 -11.95105,0.00205 -12,12l0,6c0,8.284 -0.48549,16.49691 -8.76949,16.49691l54.37857,-0.11145c-8.284,0 -8.60908,-8.10146 -8.60908,-16.38546l0,-6c0.11145,-12.08445 -4.38441,-12 -12,-12l-13,0z' fill='%23409eff'/%3E%3C/svg%3E")
       12 27 15;
 

+ 13 - 25
src/layout/components/useRenderLayout.tsx

@@ -126,7 +126,7 @@ export const useRenderLayout = () => {
 
           <ToolHeader class="flex-1"></ToolHeader>
         </div>
-        <div class="absolute left-0 top-[var(--logo-height)+1px] h-[calc(100%-1px-var(--logo-height))] w-full flex">
+        <div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-full flex">
           <Menu class="relative layout-border__right !h-full"></Menu>
           <div
             class={[
@@ -157,9 +157,9 @@ export const useRenderLayout = () => {
                     'layout-border__bottom absolute',
                     {
                       '!fixed top-0 left-0 z-10': fixedHeader.value,
-                      'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)] mt-[calc(var(--logo-height)+1px)]':
+                      'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)] mt-[var(--logo-height)]':
                         collapse.value && fixedHeader.value,
-                      'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)] mt-[calc(var(--logo-height)+1px)]':
+                      'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)] mt-[var(--logo-height)]':
                         !collapse.value && fixedHeader.value
                     }
                   ]}
@@ -190,24 +190,14 @@ export const useRenderLayout = () => {
           <Menu class="h-[var(--top-tool-height)] flex-1 px-10px"></Menu>
           <ToolHeader></ToolHeader>
         </div>
-        <div
-          class={[
-            `${prefixCls}-content`,
-            'w-full',
-            {
-              'h-[calc(100%-var(--app-footer-height))]': !fixedHeader.value,
-              'h-[calc(100%-var(--tags-view-height)-var(--app-footer-height))]': fixedHeader.value
-            }
-          ]}
-        >
+        <div class={[`${prefixCls}-content`, 'w-full h-[calc(100%-var(--top-tool-height))]']}>
           <ElScrollbar
             v-loading={pageLoading.value}
             class={[
               `${prefixCls}-content-scrollbar`,
               {
-                'mt-[var(--tags-view-height)] !pb-[calc(var(--tags-view-height)+var(--app-footer-height))]':
-                  fixedHeader.value,
-                'pb-[var(--app-footer-height)]': !fixedHeader.value
+                '!h-[calc(100%-var(--tags-view-height))] mt-[calc(var(--tags-view-height))]':
+                  fixedHeader.value
               }
             ]}
           >
@@ -216,7 +206,7 @@ export const useRenderLayout = () => {
                 class={[
                   'layout-border__bottom layout-border__top relative',
                   {
-                    '!fixed w-full top-[calc(var(--top-tool-height)+1px)] left-0': fixedHeader.value
+                    '!fixed w-full top-[var(--top-tool-height)] left-0': fixedHeader.value
                   }
                 ]}
                 style="transition: width var(--transition-time-02), left var(--transition-time-02);"
@@ -238,7 +228,7 @@ export const useRenderLayout = () => {
 
           <ToolHeader class="flex-1"></ToolHeader>
         </div>
-        <div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-[calc(100%-2px)] flex">
+        <div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-full flex">
           <TabMenu></TabMenu>
           <div
             class={[
@@ -270,18 +260,16 @@ export const useRenderLayout = () => {
               {tagsView.value ? (
                 <TagsView
                   class={[
-                    'relative layout-border__bottom layout-border__top',
+                    'relative layout-border__bottom',
                     {
                       '!fixed top-0 left-0 z-10': fixedHeader.value,
                       'w-[calc(100%-var(--tab-menu-min-width))] !left-[var(--tab-menu-min-width)] mt-[var(--logo-height)]':
-                        collapse.value && fixedHeader.value,
+                        collapse.value && fixedHeader.value && !fixedMenu.value,
                       'w-[calc(100%-var(--tab-menu-max-width))] !left-[var(--tab-menu-max-width)] mt-[var(--logo-height)]':
-                        !collapse.value && fixedHeader.value,
-                      '!fixed top-0 !left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] z-10':
-                        fixedHeader.value && fixedMenu.value,
-                      'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] !left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':
+                        !collapse.value && fixedHeader.value && !fixedMenu.value,
+                      'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] !left-[calc(var(--tab-menu-min-width)+var(--left-menu-max-width))] mt-[var(--logo-height)]':
                         collapse.value && fixedHeader.value && fixedMenu.value,
-                      'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] !left-[var(--tab-menu-max-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':
+                      'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] !left-[calc(var(--tab-menu-max-width)+var(--left-menu-max-width))] mt-[var(--logo-height)]':
                         !collapse.value && fixedHeader.value && fixedMenu.value
                     }
                   ]}

+ 8 - 8
src/utils/formatTime.ts

@@ -56,20 +56,20 @@ export const defaultShortcuts = [
  * 时间日期转换
  * @param date 当前时间,new Date() 格式
  * @param format 需要转换的时间格式字符串
- * @description format 字符串随意,如 `YYYY-mm、YYYY-mm-dd`
- * @description format 季度:"YYYY-mm-dd HH:MM:SS QQQQ"
- * @description format 星期:"YYYY-mm-dd HH:MM:SS WWW"
- * @description format 几周:"YYYY-mm-dd HH:MM:SS ZZZ"
- * @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
+ * @description format 字符串随意,如 `YYYY-MM、YYYY-MM-DD`
+ * @description format 季度:"YYYY-MM-DD HH:mm:ss QQQQ"
+ * @description format 星期:"YYYY-MM-DD HH:mm:ss WWW"
+ * @description format 几周:"YYYY-MM-DD HH:mm:ss ZZZ"
+ * @description format 季度 + 星期 + 几周:"YYYY-MM-DD HH:mm:ss WWW QQQQ ZZZ"
  * @returns 返回拼接后的时间字符串
  */
 export function formatDate(date: Date, format?: string): string {
   // 日期不存在,则返回空
   if (!date) {
-    return '';
+    return ''
   }
   // 日期存在,则进行格式化
-  return date ? dayjs(date).format(format ?? 'YYYY-MM-DD HH:mm:ss') : '';
+  return date ? dayjs(date).format(format ?? 'YYYY-MM-DD HH:mm:ss') : ''
 }
 
 /**
@@ -110,7 +110,7 @@ export function getWeek(dateTime: Date): number {
  * @description param 3天:   60 * 60* 24 * 1000 * 3
  * @returns 返回拼接后的时间字符串
  */
-export function formatPast(param: string | Date, format = 'YYYY-mm-dd HH:MM:SS'): string {
+export function formatPast(param: string | Date, format = 'YYYY-MM-DD HH:mm:ss'): string {
   // 传入格式处理、存储转换值
   let t: any, s: number
   // 获取js 时间戳

+ 588 - 0
src/views/bpm/processInstance/detail/index_new.vue

@@ -0,0 +1,588 @@
+<template>
+  <ContentWrap :bodyStyle="{ padding: '10px 20px' }" class="position-relative">
+    <!-- TODO @GoldenZqqq:建议 svgs 里面新建一个 bpm;把相关的图标放进去哈 -->
+    <Icon
+      class="!position-fixed right-80px"
+      :size="130"
+      :icon="`svg-icon:audit${processInstance.status}`"
+    />
+    <div class="text-#878c93">编号:{{ id }}</div>
+    <el-divider class="!my-8px" />
+    <div class="flex items-center gap-5 mb-10px">
+      <div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
+      <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="processInstance.status" />
+    </div>
+
+    <div class="flex items-center gap-5 mb-10px text-13px">
+      <div class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600">
+        <img class="rounded-full h-28px" src="@/assets/imgs/avatar.jpg" alt="" />
+        {{ processInstance?.startUser?.nickname }}
+      </div>
+      <div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
+    </div>
+
+    <el-tabs>
+      <!-- 表单信息 -->
+      <el-tab-pane label="表单信息">
+        <el-row :gutter="10">
+          <el-col :span="18" class="!flex !flex-col formCol">
+            <!-- 表单信息 -->
+            <div v-loading="processInstanceLoading" class="form-box flex flex-col mb-30px flex-1">
+              <!-- 情况一:流程表单 -->
+              <el-col
+                v-if="processInstance?.processDefinition?.formType === 10"
+                :offset="6"
+                :span="16"
+              >
+                <form-create
+                  v-model="detailForm.value"
+                  v-model:api="fApi"
+                  :option="detailForm.option"
+                  :rule="detailForm.rule"
+                />
+              </el-col>
+              <!-- 情况二:业务表单 -->
+              <div v-if="processInstance?.processDefinition?.formType === 20">
+                <BusinessFormComponent :id="processInstance.businessKey" />
+              </div>
+            </div>
+
+            <!-- TODO @GoldenZqqq:可以把下面 runningTask 拆一个小组件么 -->
+            <el-affix target=".formCol" position="bottom" class="h-50px" v-if="runningTask?.id">
+              <el-divider class="!mb-8px !mt-0" />
+              <div
+                class="pl-50px text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
+              >
+                <el-popover :visible="passVisible" placement="top-end" :width="500" trigger="click">
+                  <template #reference>
+                    <el-button plain type="success" @click="openPopover('1')">
+                      <Icon icon="ep:select" />&nbsp; 通过
+                    </el-button>
+                  </template>
+                  <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+                    <el-form
+                      label-position="top"
+                      class="mb-auto"
+                      ref="formRef"
+                      :model="auditForm"
+                      :rules="auditRule"
+                      label-width="100px"
+                    >
+                      <el-form-item
+                        v-if="processInstance && processInstance.startUser"
+                        label="流程发起人"
+                      >
+                        {{ processInstance?.startUser.nickname }}
+                        <el-tag size="small" type="info" class="ml-8px">
+                          {{ processInstance?.startUser.deptName }}
+                        </el-tag>
+                      </el-form-item>
+                      <el-card v-if="runningTask.formId > 0" class="mb-15px !-mt-10px">
+                        <template #header>
+                          <span class="el-icon-picture-outline">
+                            填写表单【{{ runningTask?.formName }}】
+                          </span>
+                        </template>
+                        <form-create
+                          v-model="approveForm.value"
+                          v-model:api="approveFormFApi"
+                          :option="approveForm.option"
+                          :rule="approveForm.rule"
+                        />
+                      </el-card>
+                      <el-form-item label="审批建议" prop="reason">
+                        <el-input
+                          v-model="auditForm.reason"
+                          placeholder="请输入审批建议"
+                          type="textarea"
+                        />
+                      </el-form-item>
+                      <el-form-item label="抄送人" prop="copyUserIds">
+                        <el-select
+                          v-model="auditForm.copyUserIds"
+                          multiple
+                          placeholder="请选择抄送人"
+                        >
+                          <el-option
+                            v-for="itemx in userOptions"
+                            :key="itemx.id"
+                            :label="itemx.nickname"
+                            :value="itemx.id"
+                          />
+                        </el-select>
+                      </el-form-item>
+
+                      <el-form-item>
+                        <el-button
+                          :disabled="formLoading"
+                          type="success"
+                          @click="handleAudit(true)"
+                        >
+                          通过
+                        </el-button>
+                        <el-button @click="passVisible = false"> 取消 </el-button>
+                      </el-form-item>
+                    </el-form>
+                  </div>
+                </el-popover>
+                <el-popover
+                  :visible="rejectVisible"
+                  placement="top-end"
+                  :width="500"
+                  trigger="click"
+                >
+                  <template #reference>
+                    <el-button class="mr-20px" plain type="danger" @click="openPopover('2')">
+                      <Icon icon="ep:close" />&nbsp; 拒绝
+                    </el-button>
+                  </template>
+                  <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+                    <el-form
+                      label-position="top"
+                      class="mb-auto"
+                      ref="formRef"
+                      :model="auditForm"
+                      :rules="auditRule"
+                      label-width="100px"
+                    >
+                      <el-form-item
+                        v-if="processInstance && processInstance.startUser"
+                        label="流程发起人"
+                      >
+                        {{ processInstance?.startUser.nickname }}
+                        <el-tag size="small" type="info" class="ml-8px">
+                          {{ processInstance?.startUser.deptName }}
+                        </el-tag>
+                      </el-form-item>
+                      <el-card v-if="runningTask.formId > 0" class="mb-15px !-mt-10px">
+                        <template #header>
+                          <span class="el-icon-picture-outline">
+                            填写表单【{{ runningTask?.formName }}】
+                          </span>
+                        </template>
+                        <form-create
+                          v-model="approveForm.value"
+                          v-model:api="approveFormFApi"
+                          :option="approveForm.option"
+                          :rule="approveForm.rule"
+                        />
+                      </el-card>
+                      <el-form-item label="审批建议" prop="reason">
+                        <el-input
+                          v-model="auditForm.reason"
+                          placeholder="请输入审批建议"
+                          type="textarea"
+                        />
+                      </el-form-item>
+                      <el-form-item label="抄送人" prop="copyUserIds">
+                        <el-select
+                          v-model="auditForm.copyUserIds"
+                          multiple
+                          placeholder="请选择抄送人"
+                        >
+                          <el-option
+                            v-for="itemx in userOptions"
+                            :key="itemx.id"
+                            :label="itemx.nickname"
+                            :value="itemx.id"
+                          />
+                        </el-select>
+                      </el-form-item>
+
+                      <el-form-item>
+                        <el-button
+                          :disabled="formLoading"
+                          type="danger"
+                          @click="handleAudit(false)"
+                        >
+                          拒绝
+                        </el-button>
+                        <el-button @click="rejectVisible = false"> 取消 </el-button>
+                      </el-form-item>
+                    </el-form>
+                  </div>
+                </el-popover>
+                <div @click="handleSend"> <Icon :size="14" icon="svg-icon:send" />&nbsp;抄送 </div>
+                <div @click="openTaskUpdateAssigneeForm">
+                  <Icon :size="14" icon="fa:share-square-o" />&nbsp;转交
+                </div>
+                <div @click="handleDelegate">
+                  <Icon :size="14" icon="ep:position" />&nbsp;委派
+                </div>
+                <div @click="handleSign"> <Icon :size="14" icon="ep:plus" />&nbsp;加签 </div>
+                <div @click="handleBack"> <Icon :size="14" icon="fa:mail-reply" />&nbsp;退回 </div>
+              </div>
+            </el-affix>
+
+            <!-- TODO @GoldenZqqq:后续这个,也拆个小组件出来 -->
+          </el-col>
+          <el-col :span="6">
+            <el-timeline class="pt-20px">
+              <el-timeline-item type="primary" size="large">
+                <div class="flex flex-col items-start gap-2">
+                  <div class="font-bold"> 发起人:{{ processInstance?.startUser?.nickname }}</div>
+                  <el-tag type="success">发起</el-tag>
+                  <div class="text-#a5a5a5 text-12px">
+                    发起时间:{{ formatDate(processInstance.startTime) }}
+                  </div>
+                </div>
+              </el-timeline-item>
+              <el-timeline-item
+                v-for="(activity, index) in tasks"
+                :key="index"
+                type="primary"
+                size="large"
+              >
+                <div class="flex flex-col items-start gap-2">
+                  <div class="font-bold"> 审批人:{{ activity.assigneeUser?.nickname }}</div>
+                  <dict-tag
+                    :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
+                    :value="activity.status"
+                  />
+                  <!-- TODO:暂无该字段 -->
+                  <div v-if="activity.receiveTime" class="text-#a5a5a5 text-12px">
+                    接收时间:{{ formatDate(activity.receiveTime) }}
+                  </div>
+                  <div v-if="activity.createTime" class="text-#a5a5a5 text-12px">
+                    审批时间:{{ formatDate(activity.createTime) }}
+                  </div>
+                  <div v-if="activity.opinion" class="text-#a5a5a5 text-12px w-100%">
+                    <div class="mb-5px">审批意见:</div>
+                    <div
+                      class="w-100% border-1px border-#a5a5a5 border-dashed rounded py-5px px-15px text-#2d2d2d"
+                    >
+                      {{ activity.opinion }}
+                    </div>
+                  </div>
+                </div>
+                <!-- 该节点用户的头像 -->
+                <!-- <template #dot>
+                  <img :src="activity?.avatar" alt="" />
+                </template> -->
+              </el-timeline-item>
+            </el-timeline>
+          </el-col>
+        </el-row>
+      </el-tab-pane>
+      <!-- 流程图 -->
+      <el-tab-pane label="流程图">
+        <!-- 高亮流程图 -->
+        <ProcessInstanceBpmnViewer
+          :id="`${id}`"
+          :bpmn-xml="bpmnXml"
+          :loading="processInstanceLoading"
+          :process-instance="processInstance"
+          :tasks="tasks"
+        />
+      </el-tab-pane>
+      <!-- 流转记录 -->
+      <el-tab-pane label="流转记录">
+        <!-- 审批记录 -->
+        <ProcessInstanceTaskList
+          :loading="tasksLoad"
+          :process-instance="processInstance"
+          :tasks="tasks"
+          @refresh="getTaskList"
+        />
+      </el-tab-pane>
+      <!-- 流转评论 -->
+      <el-tab-pane label="流转评论"> 流转评论 </el-tab-pane>
+    </el-tabs>
+
+    <!-- 弹窗:转派审批人 -->
+    <TaskTransferForm ref="taskTransferFormRef" @success="getDetail" />
+    <!-- 弹窗:回退节点 -->
+    <TaskReturnForm ref="taskReturnFormRef" @success="getDetail" />
+    <!-- 弹窗:委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中-->
+    <TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
+    <!-- 弹窗:加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
+    <TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" />
+  </ContentWrap>
+</template>
+<script lang="ts" setup>
+import { formatDate } from '@/utils/formatTime'
+import { DICT_TYPE } from '@/utils/dict'
+import { useUserStore } from '@/store/modules/user'
+import { setConfAndFields2 } from '@/utils/formCreate'
+import type { ApiAttrs } from '@form-create/element-ui/types/config'
+import * as DefinitionApi from '@/api/bpm/definition'
+import * as ProcessInstanceApi from '@/api/bpm/processInstance'
+import * as TaskApi from '@/api/bpm/task'
+import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
+import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
+import TaskReturnForm from './dialog/TaskReturnForm.vue'
+import TaskDelegateForm from './dialog/TaskDelegateForm.vue'
+import TaskTransferForm from './dialog/TaskTransferForm.vue'
+import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue'
+import { registerComponent } from '@/utils/routerHelper'
+import { isEmpty } from '@/utils/is'
+import * as UserApi from '@/api/system/user'
+
+defineOptions({ name: 'BpmProcessInstanceDetail' })
+
+const { query } = useRoute() // 查询参数
+const message = useMessage() // 消息弹窗
+const { proxy } = getCurrentInstance() as any
+
+const userId = useUserStore().getUser.id // 当前登录的编号
+const id = query.id as unknown as string // 流程实例的编号
+const processInstanceLoading = ref(false) // 流程实例的加载中
+const formLoading = ref(false) // 表单加载中
+const passVisible = ref(false) // 是否显示
+const rejectVisible = ref(false) // 是否显示
+const processInstance = ref<any>({}) // 流程实例
+const bpmnXml = ref('') // BPMN XML
+const tasksLoad = ref(true) // 任务的加载中
+const tasks = ref<any[]>([]) // 任务列表
+// ========== 审批信息 ==========
+const runningTask = ref<any>({}) // 运行中的任务
+const formRef = ref()
+const auditForm = ref<any>({}) // 审批任务的表单
+const auditRule = reactive({
+  reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }]
+})
+const approveForm = ref<any>({}) // 审批通过时,额外的补充信息
+const approveFormFApi = ref<any>({}) // approveForms 的 fAPi
+
+// ========== 申请信息 ==========
+const fApi = ref<ApiAttrs>() //
+const detailForm = ref({
+  rule: [],
+  option: {},
+  value: {}
+}) // 流程实例的表单详情
+
+/** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
+watch(
+  () => approveFormFApi.value,
+  (val) => {
+    val?.btn?.show(false)
+    val?.resetBtn?.show(false)
+  },
+  {
+    deep: true
+  }
+)
+
+/** 处理审批通过和不通过的操作 */
+const handleAudit = async (pass) => {
+  formLoading.value = true
+  try {
+    const auditFormRef = proxy.$refs['formRef']
+    // 1.2 校验表单
+    const elForm = unref(auditFormRef)
+    if (!elForm) return
+    const valid = await elForm.validate()
+    if (!valid) return
+
+    // 2.1 提交审批
+    const data = {
+      id: runningTask.value.id,
+      reason: auditForm.value.reason,
+      copyUserIds: auditForm.value.copyUserIds
+    }
+    if (pass) {
+      // 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
+      const formCreateApi = approveFormFApi.value
+      if (Object.keys(formCreateApi)?.length > 0) {
+        await formCreateApi.validate()
+        // @ts-ignore
+        data.variables = approveForm.value.value
+      }
+      await TaskApi.approveTask(data)
+      message.success('审批通过成功')
+    } else {
+      await TaskApi.rejectTask(data)
+      message.success('审批不通过成功')
+    }
+    // 2.2 加载最新数据
+    getDetail()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 转派审批人 */
+const taskTransferFormRef = ref()
+const openTaskUpdateAssigneeForm = () => {
+  taskTransferFormRef.value.open(runningTask.value.id)
+}
+
+/** 处理审批退回的操作 */
+const taskDelegateForm = ref()
+const handleDelegate = async () => {
+  taskDelegateForm.value.open(runningTask.value.id)
+}
+
+/** 处理审批退回的操作 */
+const taskReturnFormRef = ref()
+const handleBack = async () => {
+  taskReturnFormRef.value.open(runningTask.value.id)
+}
+
+/** 处理审批加签的操作 */
+const taskSignCreateFormRef = ref()
+const handleSign = async () => {
+  taskSignCreateFormRef.value.open(runningTask.value.id)
+}
+
+/** 获得详情 */
+const getDetail = () => {
+  // 1. 获得流程实例相关
+  getProcessInstance()
+  // 2. 获得流程任务列表(审批记录)
+  getTaskList()
+}
+
+/** 加载流程实例 */
+const BusinessFormComponent = ref<any>(null) // 异步组件
+const getProcessInstance = async () => {
+  try {
+    processInstanceLoading.value = true
+    const data = await ProcessInstanceApi.getProcessInstance(id)
+    if (!data) {
+      message.error('查询不到流程信息!')
+      return
+    }
+    processInstance.value = data
+
+    // 设置表单信息
+    const processDefinition = data.processDefinition
+    if (processDefinition.formType === 10) {
+      setConfAndFields2(
+        detailForm,
+        processDefinition.formConf,
+        processDefinition.formFields,
+        data.formVariables
+      )
+      nextTick().then(() => {
+        fApi.value?.btn.show(false)
+        fApi.value?.resetBtn.show(false)
+        fApi.value?.disabled(true)
+      })
+    } else {
+      // 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
+      BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
+    }
+
+    // 加载流程图
+    bpmnXml.value = (await DefinitionApi.getProcessDefinition(processDefinition.id))?.bpmnXml
+  } finally {
+    processInstanceLoading.value = false
+  }
+}
+
+/** 加载任务列表 */
+const getTaskList = async () => {
+  runningTask.value = {}
+  auditForm.value = {}
+  approveForm.value = {}
+  approveFormFApi.value = {}
+  try {
+    // 获得未取消的任务
+    tasksLoad.value = true
+    const data = await TaskApi.getTaskListByProcessInstanceId(id)
+    tasks.value = []
+    // 1.1 移除已取消的审批
+    data.forEach((task) => {
+      if (task.status !== 4) {
+        tasks.value.push(task)
+      }
+    })
+    // 1.2 排序,将未完成的排在前面,已完成的排在后面;
+    tasks.value.sort((a, b) => {
+      // 有已完成的情况,按照完成时间倒序
+      if (a.endTime && b.endTime) {
+        return b.endTime - a.endTime
+      } else if (a.endTime) {
+        return 1
+      } else if (b.endTime) {
+        return -1
+        // 都是未完成,按照创建时间倒序
+      } else {
+        return b.createTime - a.createTime
+      }
+    })
+
+    // 获得需要自己审批的任务
+    loadRunningTask(tasks.value)
+  } finally {
+    tasksLoad.value = false
+  }
+}
+
+/**
+ * 设置 runningTasks 中的任务
+ */
+const loadRunningTask = (tasks) => {
+  tasks.forEach((task) => {
+    if (!isEmpty(task.children)) {
+      loadRunningTask(task.children)
+    }
+    // 2.1 只有待处理才需要
+    if (task.status !== 1 && task.status !== 6) {
+      return
+    }
+    // 2.2 自己不是处理人
+    if (!task.assigneeUser || task.assigneeUser.id !== userId) {
+      return
+    }
+    // 2.3 添加到处理任务
+    runningTask.value = { ...task }
+    auditForm.value = {
+      reason: '',
+      copyUserIds: []
+    }
+
+    // 2.4 处理 approve 表单
+    if (task.formId && task.formConf) {
+      const tempApproveForm = {}
+      setConfAndFields2(tempApproveForm, task.formConf, task.formFields, task.formVariables)
+      approveForm.value = tempApproveForm
+    } else {
+      approveForm.value = {} // 占位,避免为空
+    }
+  })
+}
+
+/* 抄送 TODO */
+const handleSend = () => {}
+
+const openPopover = (flag) => {
+  passVisible.value = false
+  rejectVisible.value = false
+  formRef.value.resetFields()
+  flag === '1' ? (passVisible.value = true) : (rejectVisible.value = true)
+}
+
+/** 初始化 */
+const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
+onMounted(async () => {
+  getDetail()
+  // 获得用户列表
+  userOptions.value = await UserApi.getSimpleUserList()
+})
+</script>
+
+<style lang="scss" scoped>
+.form-box {
+  :deep(.el-card) {
+    border: none;
+  }
+}
+:deep(.el-affix--fixed) {
+  background-color: var(--el-bg-color);
+}
+
+.btn-container {
+  > div {
+    margin: 0 15px;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    &:hover {
+      color: #6db5ff;
+    }
+  }
+}
+</style>

+ 2 - 2
src/views/infra/druid/index.vue

@@ -2,8 +2,8 @@
   <doc-alert title="数据库 MyBatis" url="https://doc.iocoder.cn/mybatis/" />
   <doc-alert title="多数据源(读写分离)" url="https://doc.iocoder.cn/dynamic-datasource/" />
 
-  <ContentWrap>
-    <IFrame v-if="!loading" :src="url" />
+  <ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
+    <IFrame v-if="!loading" v-loading="loading" :src="url" />
   </ContentWrap>
 </template>
 <script lang="ts" setup>

+ 1 - 1
src/views/infra/server/index.vue

@@ -1,7 +1,7 @@
 <template>
   <doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
 
-  <ContentWrap>
+  <ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
     <IFrame v-if="!loading" v-loading="loading" :src="src" />
   </ContentWrap>
 </template>

+ 1 - 1
src/views/infra/skywalking/index.vue

@@ -1,7 +1,7 @@
 <template>
   <doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
 
-  <ContentWrap>
+  <ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
     <IFrame v-if="!loading" v-loading="loading" :src="src" />
   </ContentWrap>
 </template>

+ 2 - 2
src/views/infra/swagger/index.vue

@@ -1,8 +1,8 @@
 <template>
   <doc-alert title="接口文档" url="https://doc.iocoder.cn/api-doc/" />
 
-  <ContentWrap>
-    <IFrame :src="src" />
+  <ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
+    <IFrame v-if="!loading" v-loading="loading" :src="src" />
   </ContentWrap>
 </template>
 <script lang="ts" setup>

+ 27 - 0
src/views/mall/promotion/components/SpuAndSkuList.vue

@@ -29,6 +29,17 @@
     </el-table-column>
     <el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
     <el-table-column align="center" label="库存" min-width="90" prop="stock" />
+    <el-table-column v-if="spuData.length > 1 && isDelete" align="center" label="操作" min-width="90" >
+      <template #default="scope">
+        <el-button
+          type="primary"
+          link
+          @click="deleteSpu(scope.row.id)"
+        >
+          删除
+        </el-button>
+      </template>
+    </el-table-column>
   </el-table>
 </template>
 <script generic="T extends Spu" lang="ts" setup>
@@ -40,10 +51,13 @@ import { SpuProperty } from '@/views/mall/promotion/components/index'
 
 defineOptions({ name: 'PromotionSpuAndSkuList' })
 
+const message = useMessage() // 消息弹窗
+
 const props = defineProps<{
   spuList: T[]
   ruleConfig: RuleConfig[]
   spuPropertyListP: SpuProperty<T>[]
+  isDelete?: boolean //spu是否可以多选
 }>()
 
 const spuData = ref<Spu[]>([]) // spu 详情数据列表
@@ -77,6 +91,19 @@ const imagePreview = (imgUrl: string) => {
   })
 }
 
+// 删除时的触发事件
+const emits = defineEmits<{
+  (e: 'delete', spuId: number): void
+}>()
+
+/** 多选时可以删除spu **/
+const deleteSpu = async (spuId: number) => {
+  await message.confirm('是否删除商品编号为' + spuId + '的数据?')
+  let index = spuData.value.findIndex((item) => item.id == spuId)
+  spuData.value.splice(index,1);
+  emits('delete',spuId)
+}
+
 /**
  * 将传进来的值赋值给 skuList
  */

+ 46 - 9
src/views/mall/promotion/discountActivity/DiscountActivityForm.vue

@@ -19,6 +19,8 @@
           :rule-config="ruleConfig"
           :spu-list="spuList"
           :spu-property-list-p="spuPropertyList"
+          :isDelete="true"
+          @delete="deleteSpu"
         >
           <el-table-column align="center" label="优惠金额" min-width="168">
             <template #default="{ row: sku }">
@@ -47,6 +49,7 @@ import { cloneDeep } from 'lodash-es'
 import * as DiscountActivityApi from '@/api/mall/promotion/discount/discountActivity'
 import * as ProductSpuApi from '@/api/mall/product/spu'
 import { getPropertyList, RuleConfig } from '@/views/mall/product/spu/components'
+import {formatToFraction} from "@/utils";
 
 defineOptions({ name: 'PromotionDiscountActivityForm' })
 
@@ -65,8 +68,8 @@ const spuAndSkuListRef = ref() // sku 限时折扣  配置组件Ref
 const ruleConfig: RuleConfig[] = []
 const spuList = ref<DiscountActivityApi.SpuExtension[]>([]) // 选择的 spu
 const spuPropertyList = ref<SpuProperty<DiscountActivityApi.SpuExtension>[]>([])
+const spuIds = ref<number[]>([]);
 const selectSpu = (spuId: number, skuIds: number[]) => {
-  formRef.value.setValues({ spuId })
   getSpuDetails(spuId, skuIds)
 }
 /**
@@ -75,14 +78,22 @@ const selectSpu = (spuId: number, skuIds: number[]) => {
 const getSpuDetails = async (
   spuId: number,
   skuIds: number[] | undefined,
-  products?: DiscountActivityApi.DiscountProductVO[]
+  products?: DiscountActivityApi.DiscountProductVO[],
+  type?: string
 ) => {
-  const spuProperties: SpuProperty<DiscountActivityApi.SpuExtension>[] = []
+  //如果已经包含spu则跳过
+  if(spuIds.value.includes(spuId)){
+    if(type !== "load"){
+      message.error("数据重复选择!")
+    }
+    return;
+  }
+  spuIds.value.push(spuId)
   const res = (await ProductSpuApi.getSpuDetailList([spuId])) as DiscountActivityApi.SpuExtension[]
   if (res.length == 0) {
     return
   }
-  spuList.value = []
+  //spuList.value = []
   // 因为只能选择一个
   const spu = res[0]
   const selectSkus =
@@ -100,15 +111,19 @@ const getSpuDetails = async (
       config = product || config
     }
     sku.productConfig = config
+    sku.price = formatToFraction(sku.price)
+    sku.marketPrice = formatToFraction(sku.marketPrice)
+    sku.costPrice = formatToFraction(sku.costPrice)
+    sku.firstBrokeragePrice = formatToFraction(sku.firstBrokeragePrice)
+    sku.secondBrokeragePrice = formatToFraction(sku.secondBrokeragePrice)
   })
   spu.skus = selectSkus as DiscountActivityApi.SkuExtension[]
-  spuProperties.push({
+  spuPropertyList.value.push({
     spuId: spu.id!,
     spuDetail: spu,
     propertyList: getPropertyList(spu)
   })
   spuList.value.push(spu)
-  spuPropertyList.value = spuProperties
 }
 
 // ================= end =================
@@ -126,8 +141,10 @@ const open = async (type: string, id?: number) => {
       const data = (await DiscountActivityApi.getDiscountActivity(
         id
       )) as DiscountActivityApi.DiscountActivityVO
-      const supId = data.products[0].spuId
-      await getSpuDetails(supId!, data.products?.map((sku) => sku.skuId), data.products)
+      for (let productsKey in data.products) {
+        const supId = data.products[productsKey].spuId
+        await getSpuDetails(supId!, data.products?.map((sku) => sku.skuId), data.products,"load")
+      }
       formRef.value.setValues(data)
     } finally {
       formLoading.value = false
@@ -149,9 +166,20 @@ const submitForm = async () => {
     const data = formRef.value.formModel as DiscountActivityApi.DiscountActivityVO
     // 获取 折扣商品配置
     const products = cloneDeep(spuAndSkuListRef.value.getSkuConfigs('productConfig'))
+    let timp = false;
     products.forEach((item: DiscountActivityApi.DiscountProductVO) => {
-      item.discountType = data['discountType']
+      if(item.discountPrice != null && item.discountPrice > 0){
+        item.discountType = 1
+      }else if(item.discountPercent != null && item.discountPercent > 0){
+        item.discountType = 2
+      }else{
+        timp = true
+      }
     })
+    if(timp){
+      message.error("优惠金额和折扣百分比需要填写一个");
+      return;
+    }
     data.products = products
     // 真正提交
     if (formType.value === 'create') {
@@ -173,7 +201,16 @@ const submitForm = async () => {
 const resetForm = async () => {
   spuList.value = []
   spuPropertyList.value = []
+  spuIds.value = []
   await nextTick()
   formRef.value.getElFormRef().resetFields()
 }
+
+/**
+ * 删除spu
+ */
+const deleteSpu = (spuId: number) => {
+  spuIds.value.splice(spuIds.value.findIndex((item) => item == spuId), 1)
+  spuPropertyList.value.splice(spuPropertyList.value.findIndex((item) => item.spuId == spuId), 1)
+}
 </script>

+ 0 - 11
src/views/mall/promotion/discountActivity/discountActivity.data.ts

@@ -72,17 +72,6 @@ const crudSchemas = reactive<CrudSchema[]>([
       width: 120
     }
   },
-  {
-    label: '优惠类型',
-    field: 'discountType',
-    dictType: DICT_TYPE.PROMOTION_DISCOUNT_TYPE,
-    dictClass: 'number',
-    isSearch: true,
-    form: {
-      component: 'Radio',
-      value: 1
-    }
-  },
   {
     label: '活动商品',
     field: 'spuId',

+ 11 - 11
src/views/mall/promotion/discountActivity/index.vue

@@ -70,17 +70,17 @@
           ~ {{ formatDate(scope.row.endTime, 'YYYY-MM-DD') }}
         </template>
       </el-table-column>
-      <el-table-column label="商品图片" prop="spuName" min-width="80">
-        <template #default="scope">
-          <el-image
-            :src="scope.row.picUrl"
-            class="h-40px w-40px"
-            :preview-src-list="[scope.row.picUrl]"
-            preview-teleported
-          />
-        </template>
-      </el-table-column>
-      <el-table-column label="商品标题" prop="spuName" min-width="300" />
+<!--      <el-table-column label="商品图片" prop="spuName" min-width="80">-->
+<!--        <template #default="scope">-->
+<!--          <el-image-->
+<!--            :src="scope.row.picUrl"-->
+<!--            class="h-40px w-40px"-->
+<!--            :preview-src-list="[scope.row.picUrl]"-->
+<!--            preview-teleported-->
+<!--          />-->
+<!--        </template>-->
+<!--      </el-table-column>-->
+<!--      <el-table-column label="商品标题" prop="spuName" min-width="300" />-->
       <el-table-column label="活动状态" align="center" prop="status" min-width="100">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />

+ 7 - 6
src/views/mall/promotion/kefu/components/KeFuMessageList.vue

@@ -71,6 +71,7 @@
                 <MessageItem :message="item">
                   <ProductItem
                     v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
+                    :spuId="getMessageContent(item).spuId"
                     :picUrl="getMessageContent(item).picUrl"
                     :price="getMessageContent(item).price"
                     :skuText="getMessageContent(item).introduction"
@@ -393,7 +394,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
           border-left: 5px solid transparent;
           border-bottom: 5px solid transparent;
           border-top: 5px solid transparent;
-          border-right: 5px solid #ffffff;
+          border-right: 5px solid var(--app-content-bg-color);
         }
       }
     }
@@ -412,7 +413,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
           right: -19px;
           top: calc(50% - 10px);
           position: absolute;
-          border-left: 5px solid #ffffff;
+          border-left: 5px solid var(--app-content-bg-color);
           border-bottom: 5px solid transparent;
           border-top: 5px solid transparent;
           border-right: 5px solid transparent;
@@ -422,9 +423,9 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
 
     // 消息气泡
     .kefu-message {
-      color: #333;
+      color: #A9A9A9;
       border-radius: 5px;
-      box-shadow: 3px 5px 15px rgba(0, 0, 0, 0.2);
+      box-shadow: 3px 3px 5px rgba(220,220,220, 0.1);
       padding: 5px 10px;
       width: auto;
       max-width: 50%;
@@ -432,7 +433,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
       display: inline-block !important;
       position: relative;
       word-break: break-all;
-      background-color: #ffffff;
+      background-color: var(--app-content-bg-color);
       transition: all 0.2s;
 
       &:hover {
@@ -454,7 +455,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
 
   .chat-tools {
     width: 100%;
-    border: #e4e0e0 solid 1px;
+    border: var(--el-border-color) solid 1px;
     border-radius: 10px;
     height: 44px;
   }

+ 1 - 0
src/views/mall/promotion/kefu/components/history/ProductBrowsingHistory.vue

@@ -1,6 +1,7 @@
 <template>
   <ProductItem
     v-for="item in list"
+    :spu-id="item.spuId"
     :key="item.id"
     :picUrl="item.picUrl"
     :price="item.price"

+ 11 - 2
src/views/mall/promotion/kefu/components/message/OrderItem.vue

@@ -1,5 +1,5 @@
 <template>
-  <div v-if="isObject(getMessageContent)">
+  <div v-if="isObject(getMessageContent)" @click="openDetail(getMessageContent.id)" style="cursor: pointer;">
     <div :key="getMessageContent.id" class="order-list-card-box mt-14px">
       <div class="order-card-header flex items-center justify-between p-x-5px">
         <div class="order-no">订单号:{{ getMessageContent.no }}</div>
@@ -9,6 +9,7 @@
       </div>
       <div v-for="item in getMessageContent.items" :key="item.id" class="border-bottom">
         <ProductItem
+          :spu-id="item.spuId"
           :num="item.count"
           :picUrl="item.picUrl"
           :price="item.price"
@@ -36,6 +37,8 @@ import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
 import { isObject } from '@/utils/is'
 import ProductItem from '@/views/mall/promotion/kefu/components/message/ProductItem.vue'
 
+const { push } = useRouter()
+
 defineOptions({ name: 'OrderItem' })
 const props = defineProps<{
   message?: KeFuMessageRespVO
@@ -46,6 +49,12 @@ const getMessageContent = computed(() =>
   typeof props.message !== 'undefined' ? jsonParse(props!.message!.content) : props.order
 )
 
+/** 查看订单详情 */
+const openDetail = (id: number) => {
+  console.log(getMessageContent)
+  push({ name: 'TradeOrderDetail', params: { id } })
+}
+
 /**
  * 格式化订单状态的颜色
  *
@@ -97,7 +106,7 @@ function formatOrderStatus(order: any) {
 .order-list-card-box {
   border-radius: 10px;
   padding: 10px;
-  border: 1px #6a6a6a solid;
+  border: 1px var(--el-border-color) solid;
   background-color: var(--app-content-bg-color);
 
   .order-card-header {

+ 16 - 3
src/views/mall/promotion/kefu/components/message/ProductItem.vue

@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div @click.stop="openDetail(props.spuId)" style="cursor: pointer;">
     <div>
       <slot name="top"></slot>
     </div>
@@ -15,6 +15,7 @@
           class="order-img"
           fit="contain"
           preview-teleported
+          @click.stop
         />
       </div>
       <div
@@ -53,8 +54,14 @@
 <script lang="ts" setup>
 import { fenToYuan } from '@/utils'
 
+const { push } = useRouter()
+
 defineOptions({ name: 'ProductItem' })
 const props = defineProps({
+  spuId: {
+    type: Number,
+    default: 0
+  },
   picUrl: {
     type: String,
     default: 'https://img1.baidu.com/it/u=1601695551,235775011&fm=26&fmt=auto'
@@ -107,13 +114,19 @@ const skuString = computed(() => {
   }
   return props.skuText
 })
+
+/** 查看商品详情 */
+const openDetail = (spuId: number) => {
+  console.log(props.spuId)
+  push({ name: 'ProductSpuDetail', params: { id: spuId } })
+}
 </script>
 
 <style lang="scss" scoped>
 .ss-order-card-warp {
   padding: 20px;
   border-radius: 10px;
-  border: 1px #6a6a6a solid;
+  border: 1px var(--el-border-color) solid;
   background-color: var(--app-content-bg-color);
 
   .img-box {
@@ -134,7 +147,7 @@ const skuString = computed(() => {
 
     .tool-box {
       position: absolute;
-      right: 0px;
+      right: 0;
       bottom: -10px;
     }
   }

+ 2 - 2
src/views/mall/trade/afterSale/detail/index.vue

@@ -325,13 +325,13 @@ onMounted(async () => {
     align-items: center;
     min-height: 30px;
     padding: 10px;
-    background-color: #f7f8fa;
+    background-color: var(--app-content-bg-color);
 
     &::before {
       position: absolute;
       top: 10px;
       left: 13px;
-      border-color: transparent #f7f8fa transparent transparent; /* 尖角颜色,左侧朝向 */
+      border-color: transparent var(--app-content-bg-color) transparent transparent; /* 尖角颜色,左侧朝向 */
       border-style: solid;
       border-width: 8px; /* 调整尖角大小 */
       content: '';

+ 2 - 13
src/views/mall/trade/delivery/pickUpStore/PickUpStoreForm.vue

@@ -106,14 +106,8 @@
       <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
       <el-button @click="dialogVisible = false">取 消</el-button>
     </template>
-    <el-dialog
-      v-model="mapDialogVisible"
-      title="获取经纬度"
-      append-to-body
-      width="500px"
-      class="mapBox"
-    >
-      <iframe id="mapPage" width="100%" height="100%" frameborder="0" :src="tencentLbsUrl"></iframe>
+    <el-dialog v-model="mapDialogVisible" title="获取经纬度" append-to-body>
+      <IFrame class="h-609px" :src="tencentLbsUrl" />
     </el-dialog>
   </Dialog>
 </template>
@@ -266,8 +260,3 @@ onMounted(async () => {
   await initTencentLbsMap()
 })
 </script>
-<style lang="scss">
-.mapBox .el-dialog__body {
-  height: 640px !important;
-}
-</style>

+ 3 - 1
src/views/report/goview/index.vue

@@ -2,7 +2,9 @@
   <ContentWrap>
     <doc-alert title="大屏设计器" url="https://doc.iocoder.cn/report/screen/" />
 
-    <IFrame :src="src" />
+    <ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
+      <IFrame :src="src" />
+    </ContentWrap>
   </ContentWrap>
 </template>
 <script lang="ts" setup>

+ 4 - 3
src/views/report/jmreport/index.vue

@@ -2,7 +2,9 @@
   <ContentWrap>
     <doc-alert title="报表设计器" url="https://doc.iocoder.cn/report/" />
 
-    <IFrame :src="src" />
+    <ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
+      <IFrame :src="src" />
+    </ContentWrap>
   </ContentWrap>
 </template>
 <script lang="ts" setup>
@@ -10,6 +12,5 @@ import { getAccessToken } from '@/utils/auth'
 
 defineOptions({ name: 'JimuReport' })
 
-const BASE_URL = import.meta.env.VITE_BASE_URL
-const src = ref(BASE_URL + '/jmreport/list?token=' + getAccessToken())
+const src = ref(import.meta.env.VITE_BASE_URL + '/jmreport/list?token=' + getAccessToken())
 </script>

+ 1 - 8
src/views/system/role/RoleAssignMenuForm.vue

@@ -8,7 +8,7 @@
         <el-tag>{{ formData.code }}</el-tag>
       </el-form-item>
       <el-form-item label="菜单权限">
-        <el-card class="cardHeight">
+        <el-card class="w-full h-400px !overflow-y-scroll" shadow="never">
           <template #header>
             全选/全不选:
             <el-switch
@@ -151,10 +151,3 @@ const handleCheckedTreeExpand = () => {
   }
 }
 </script>
-<style lang="scss" scoped>
-.cardHeight {
-  width: 100%;
-  max-height: 400px;
-  overflow-y: scroll;
-}
-</style>

+ 3 - 3
src/views/system/role/RoleDataPermissionForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <Dialog v-model="dialogVisible" title="菜单权限" width="800">
+  <Dialog v-model="dialogVisible" title="数据权限" width="800">
     <el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
       <el-form-item label="角色名称">
         <el-tag>{{ formData.name }}</el-tag>
@@ -21,9 +21,9 @@
     <el-form-item
       v-if="formData.dataScope === SystemDataScopeEnum.DEPT_CUSTOM"
       label="权限范围"
-      style="display: flex"
+      label-width="80px"
     >
-      <el-card class="card" shadow="never">
+      <el-card class="w-full h-400px !overflow-y-scroll" shadow="never">
         <template #header>
           全选/全不选:
           <el-switch

+ 1 - 8
src/views/system/tenantPackage/TenantPackageForm.vue

@@ -11,7 +11,7 @@
         <el-input v-model="formData.name" placeholder="请输入套餐名" />
       </el-form-item>
       <el-form-item label="菜单权限">
-        <el-card class="cardHeight">
+        <el-card class="w-full h-400px !overflow-y-scroll" shadow="never">
           <template #header>
             全选/全不选:
             <el-switch
@@ -185,10 +185,3 @@ const handleCheckedTreeExpand = () => {
   }
 }
 </script>
-<style lang="scss" scoped>
-.cardHeight {
-  width: 100%;
-  max-height: 400px;
-  overflow-y: scroll;
-}
-</style>

+ 1 - 1
uno.config.ts

@@ -12,7 +12,7 @@ export default defineConfig({
 ${selector} {
   display: flex;
   height: 100%;
-  padding: 1px 10px 0;
+  padding: 0 10px;
   cursor: pointer;
   align-items: center;
   transition: background var(--transition-time-02);

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff