Explorar el Código

Merge branch 'admin_dev' of http://112.213.107.185:3000/cwg-crm/gypsy-crm-frontend-vu into admin_dev

zhb hace 1 mes
padre
commit
2e2318ffc0

+ 69 - 0
components/cwg-droplist-item.vue

@@ -0,0 +1,69 @@
+<template>
+  <view class="cwg-droplist-item" :class="{ 'is-disabled': disabled }" @click.stop="handleClick">
+    <slot></slot>
+  </view>
+</template>
+
+<script setup>
+import { getCurrentInstance } from 'vue'
+
+const props = defineProps({
+  command: {
+    type: [String, Number, Object],
+    default: ''
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const instance = getCurrentInstance()
+const emit = defineEmits(['click'])
+
+const handleClick = () => {
+  if (props.disabled) return
+  
+  emit('click')
+  
+  // 查找父级的 cwg-droplist 实例并触发事件
+  let parent = instance.parent
+  while (parent) {
+    if (parent.type.__file && parent.type.__file.includes('cwg-droplist.vue')) {
+      if (parent.exposed && parent.exposed.handleItemClick) {
+        parent.exposed.handleItemClick(props.command)
+      }
+      break
+    }
+    parent = parent.parent
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import '@/uni.scss';
+
+.cwg-droplist-item {
+  display: flex;
+  align-items: center;
+  padding: px2rpx(10) px2rpx(20);
+  font-size: px2rpx(14);
+  line-height: px2rpx(22);
+  color: #606266;
+  white-space: nowrap;
+  cursor: pointer;
+  transition: background-color 0.2s, color 0.2s;
+  box-sizing: border-box;
+
+  &:hover, &:active {
+    background-color: #ecf5ff;
+    color: #409eff;
+  }
+
+  &.is-disabled {
+    color: #c0c4cc;
+    cursor: not-allowed;
+    background-color: transparent;
+  }
+}
+</style>

+ 314 - 0
components/cwg-droplist.vue

@@ -0,0 +1,314 @@
+<template>
+  <view class="cwg-droplist">
+    <view class="cwg-droplist-trigger" @click.stop="toggleMenu" id="trigger-box">
+      <slot></slot>
+    </view>
+
+    <!-- 使用 teleport 解决层级遮挡问题 (H5) -->
+    <teleport to="body" :disabled="!isTeleportSupported">
+      <view
+        :id="portalId"
+        class="cwg-droplist-portal"
+        :class="{ 'is-show': isShow }"
+        :style="{ zIndex }"
+        @click.stop="closeMenu"
+      >
+        <view class="cwg-droplist-mask" @click.stop="closeMenu"></view>
+        <view
+          class="cwg-droplist-menu"
+          :class="[`placement-${placement}`]"
+          :style="menuStyle"
+          @click.stop
+        >
+          <slot name="dropdown">
+            <template v-if="menuList && menuList.length">
+              <cwg-droplist-item
+                v-for="(item, index) in menuList"
+                :key="index"
+                :command="item.command !== undefined ? item.command : (item.value !== undefined ? item.value : item)"
+                :disabled="item.disabled"
+                @click="emit('menuClick', { value: item, index })"
+              >
+                {{ item.label || item.text || item.title || item }}
+              </cwg-droplist-item>
+            </template>
+          </slot>
+        </view>
+      </view>
+    </teleport>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, getCurrentInstance, onUnmounted, nextTick } from 'vue'
+
+// 导入 cwg-droplist-item 以便在组件内部直接使用
+import CwgDroplistItem from './cwg-droplist-item.vue'
+
+const props = defineProps({
+  // 传入的菜单列表,支持简单数组 ['A', 'B'] 或对象数组 [{ label: 'A', command: 'a', disabled: true, row: row }]
+  menuList: {
+    type: Array,
+    default: () => []
+  },
+  // 菜单弹出位置,支持: bottom, bottom-start, bottom-end, top, top-start, top-end
+  placement: {
+    type: String,
+    default: 'bottom'
+  },
+  // 距离触发元素的间距 (px)
+  offset: {
+    type: Number,
+    default: 60
+  },
+  // z-index 层级
+  zIndex: {
+    type: Number,
+    default: 9999
+  },
+  // 点击菜单项后是否自动隐藏菜单
+  hideOnClick: {
+    type: Boolean,
+    default: true
+  }
+})
+
+const emit = defineEmits(['visible-change', 'command', 'menuClick'])
+
+const instance = getCurrentInstance()
+const isShow = ref(false)
+const menuStyle = ref({})
+
+// 检测环境是否支持 Teleport
+const isTeleportSupported = computed(() => {
+  // #ifdef H5
+  return true
+  // #endif
+  return false
+})
+
+// 查询 DOM 节点信息
+const queryRect = (selector, context = instance.proxy) => {
+  return new Promise((resolve) => {
+    uni.createSelectorQuery()
+      .in(context)
+      .select(selector)
+      .boundingClientRect(resolve)
+      .exec()
+  })
+}
+
+// 获取唯一标识符,用于 teleport 查询
+const portalId = 'droplist_' + Math.random().toString(36).substr(2, 9)
+
+// 展开/收起菜单
+const toggleMenu = async () => {
+  if (isShow.value) {
+    closeMenu()
+  } else {
+    await openMenu()
+  }
+}
+
+// 展开菜单并计算位置
+const openMenu = async () => {
+  const triggerRectList = await queryRect('.cwg-droplist-trigger')
+  const triggerRect = Array.isArray(triggerRectList) ? triggerRectList[0] : triggerRectList
+  if (!triggerRect) return
+
+  // 预设菜单显示,获取菜单尺寸
+  isShow.value = true
+  emit('visible-change', true)
+  
+  await nextTick()
+
+  let menuRect = null
+  // #ifdef H5
+  // 在 H5 且 teleport 生效时,需要通过全局原生 API 获取尺寸
+  const el = document.querySelector(`#${portalId} .cwg-droplist-menu`)
+  if (el) menuRect = el.getBoundingClientRect()
+  // #endif
+  // #ifndef H5
+  let menuRectList = await queryRect('.cwg-droplist-menu', instance.proxy)
+  menuRect = Array.isArray(menuRectList) ? menuRectList[0] : menuRectList
+  // #endif
+
+  if (!menuRect) return
+
+// 获取屏幕尺寸
+  const sysInfo = uni.getSystemInfoSync()
+  // 注意 windowHeight 可能受到键盘、导航栏影响,加上 windowTop 偏移更准确(特别是在 H5 中)
+  const windowHeight = sysInfo.windowHeight
+  const windowWidth = sysInfo.windowWidth
+
+  const { left, right, top, bottom, width: triggerWidth, height: triggerHeight } = triggerRect
+  const menuWidth = menuRect.width
+  const menuHeight = menuRect.height
+
+  let finalTop = 0
+  let finalLeft = 0
+
+  // 计算垂直位置(基于视口)
+  if (props.placement.startsWith('top')) {
+    finalTop = top - menuHeight - props.offset
+    // 防溢出检测
+    if (finalTop < 0) {
+      finalTop = bottom + props.offset
+    }
+  } else { // bottom
+    finalTop = bottom + props.offset
+    // 防溢出检测:如果下方空间不够,且上方空间足够,向上翻转
+    if (finalTop + menuHeight > windowHeight && top - menuHeight - props.offset > 0) {
+      finalTop = top - menuHeight - props.offset
+    }
+  }
+
+  // 计算水平位置
+  if (props.placement.endsWith('start')) {
+    finalLeft = left
+  } else if (props.placement.endsWith('end')) {
+    finalLeft = right - menuWidth
+  } else { // center
+    finalLeft = left + (triggerWidth / 2) - (menuWidth / 2)
+  }
+
+  // 水平防溢出检测
+  if (finalLeft < 10) finalLeft = 10
+  if (finalLeft + menuWidth > windowWidth - 10) {
+    finalLeft = windowWidth - menuWidth - 10
+  }
+
+  menuStyle.value = {
+    top: `${finalTop}px`,
+    left: `${finalLeft}px`,
+    opacity: 1,
+    transform: 'scaleY(1)'
+  }
+}
+
+// 关闭菜单
+const closeMenu = () => {
+  isShow.value = false
+  menuStyle.value = {
+    ...menuStyle.value,
+    opacity: 0,
+    transform: 'scaleY(0)'
+  }
+  emit('visible-change', false)
+}
+
+// 提供给子组件 (menu-item) 调用的方法
+const handleItemClick = (command) => {
+  emit('command', command)
+  if (props.hideOnClick) {
+    closeMenu()
+  }
+}
+
+// 暴露给外部或子组件的方法
+defineExpose({
+  closeMenu,
+  handleItemClick
+})
+
+</script>
+
+<style lang="scss" scoped>
+@import '@/uni.scss';
+
+.cwg-droplist {
+  display: inline-block;
+  position: relative;
+}
+
+.cwg-droplist-trigger {
+  display: inline-block;
+  cursor: pointer;
+}
+
+.cwg-droplist-portal {
+  position: fixed;
+  top: 0;
+  /* 解决某些浏览器下的 H5 顶部导航栏导致 fixed 参考系下移的问题 */
+  /* #ifdef H5 */
+  top: var(--window-top);
+  /* #endif */
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  pointer-events: none;
+  visibility: hidden;
+
+  &.is-show {
+    pointer-events: auto;
+    visibility: visible;
+  }
+}
+
+.cwg-droplist-mask {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: transparent;
+}
+
+.cwg-droplist-menu {
+  position: absolute;
+  background-color: #fff;
+  border: 1px solid #ebeef5;
+  border-radius: px2rpx(4);
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  padding: px2rpx(10) 0;
+  min-width: px2rpx(100);
+  opacity: 0;
+  transform: scaleY(0);
+  transition: opacity 0.3s, transform 0.3s;
+  box-sizing: border-box;
+
+  /* 动画基点设置 */
+  &.placement-bottom,
+  &.placement-bottom-start,
+  &.placement-bottom-end {
+    transform-origin: top center;
+  }
+
+  &.placement-top,
+  &.placement-top-start,
+  &.placement-top-end {
+    transform-origin: bottom center;
+  }
+
+  /* 小箭头 (可选) */
+  &::before {
+    content: '';
+    position: absolute;
+    width: 0;
+    height: 0;
+    border: 6px solid transparent;
+  }
+
+  &.placement-bottom::before,
+  &.placement-bottom-start::before,
+  &.placement-bottom-end::before {
+    top: -12px;
+    border-bottom-color: #ebeef5;
+  }
+
+  &.placement-bottom::before { left: 50%; transform: translateX(-50%); }
+  &.placement-bottom-start::before { left: 15px; }
+  &.placement-bottom-end::before { right: 15px; }
+
+  &.placement-top::before,
+  &.placement-top-start::before,
+  &.placement-top-end::before {
+    bottom: -12px;
+    border-top-color: #ebeef5;
+  }
+  
+  &.placement-top::before { left: 50%; transform: translateX(-50%); }
+  &.placement-top-start::before { left: 15px; }
+  &.placement-top-end::before { right: 15px; }
+}
+</style>

+ 45 - 24
components/cwg-tabel.vue

@@ -39,18 +39,16 @@
                                 <text>{{ getNoteText(row, locale, userStore) }}</text>
                             </view>
                             <view v-else-if="column.type === 'action'" class="action-wrapper">
-                                <view class="action-list">
-                                    <template v-for="(item, idx) in getVisibleActions(column.menuList, row)" :key="idx">
-                                        <text v-if="actionExpanded[`${rowIndex}-${column.prop}`] || idx < 2"
-                                            class="action-btn" @click.stop="item.btnClick && item.btnClick(row)">
-                                            {{ item.label || item.text || item.name }}
-                                        </text>
-                                    </template>
-                                    <text v-if="getVisibleActions(column.menuList, row).length > 2"
-                                        class="action-toggle-btn" @click.stop="toggleAction(rowIndex, column.prop)">
-                                        {{ actionExpanded[`${rowIndex}-${column.prop}`] ? '隐藏' : '更多' }}
-                                    </text>
-                                </view>
+                                <cwg-droplist
+                                    v-if="getComputedMenuList(column.menuList, row).length > 0"
+                                    :menuList="getComputedMenuList(column.menuList, row)"
+                                    placement="bottom-end"
+                                    @menuClick="(payload) => handleActionClick(payload, row)"
+                                >
+                                    <view class="action-trigger">
+                                        <cwg-icon name="crm-ellipsis" :size="24" color="#000" />
+                                    </view>
+                                </cwg-droplist>
                             </view>
                             <template v-else-if="column.type === 'more'">
                                 <cwg-icon v-if="isMobile" name="crm-chevron-down" class="crm-chevron-down" :size="16" color="#007" />
@@ -281,9 +279,8 @@ const isMobile = ref(false)
 // 排序状态
 const sortState = ref({ prop: '', order: '' }) // order: 'asc' | 'desc' | ''
 
-// action 列的状态
-const actionExpanded = ref({})
-const getVisibleActions = (menuList, row) => {
+// action 列的状态处理
+const getComputedMenuList = (menuList, row) => {
     if (!menuList) return []
     return menuList.filter(item => {
         if (typeof item.show === 'function') {
@@ -292,9 +289,13 @@ const getVisibleActions = (menuList, row) => {
         return item.show !== false
     })
 }
-const toggleAction = (rowIndex, prop) => {
-    const key = `${rowIndex}-${prop}`
-    actionExpanded.value[key] = !actionExpanded.value[key]
+
+const handleActionClick = (payload, row) => {
+    const { value } = payload
+    // 如果你在 menuList 中定义了 btnClick,则直接调用并传入当前行数据
+    if (value && typeof value.btnClick === 'function') {
+        value.btnClick(row)
+    }
 }
 
 // ========== 计算属性 ==========
@@ -727,12 +728,32 @@ defineExpose({
 
 .table-container {
     width: 100%;
-    // overflow-x: auto;
-    // -webkit-overflow-scrolling: touch;
+     overflow-x: auto;
+     -webkit-overflow-scrolling: touch;
     margin-top: px2rpx(20);
     max-height: calc(100vh - 209px);
     color: var(--color-slate-800);
 
+    .action-wrapper {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+
+        .action-trigger {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            cursor: pointer;
+            padding: px2rpx(4);
+            border-radius: px2rpx(4);
+            transition: background-color 0.2s;
+
+            &:hover {
+                background-color: rgba(0, 0, 0, 0.05);
+            }
+        }
+    }
+
     .table-loading-mask {
         width: 100%;
         height: 100%;
@@ -751,11 +772,11 @@ defineExpose({
 
         /* 强制显示滚动条并美化 */
         &::-webkit-scrollbar {
-            width: 8px;
-            height: 8px;
-            display: block;
+            width: 4px!important;
+            height: 4px!important;
+            display: block!important;
             /* 强制在某些webkit浏览器中显示 */
-            background-color: transparent;
+            background-color: transparent!important;
         }
 
         &::-webkit-scrollbar-track {

+ 5 - 3
pages/ib/components/applyIbDialog.vue

@@ -7,7 +7,7 @@
                       :placeholder="t('placeholder.choose')" filterable @change="changeCustomer" style="max-width: 280px" />
         </uni-forms-item>
         <uni-loading v-if="laoding"/>
-        <view v-else class="commission-table-container">
+        <view v-else class="commission-table-container" v-if="addAgentForm.customerId">
           <table class="commission-table">
             <thead>
               <tr>
@@ -72,6 +72,7 @@
             </tbody>
           </table>
         </view>
+        <view v-else style="height: 50px"/>
       </uni-forms>
     </view>
   </cwg-popup>
@@ -137,8 +138,9 @@
   watch(() => props.visible, async (val) => {
     if (val) {
       laoding.value = true
-      await loadCustomerList()
-
+      if (props.isFormApplyIb){
+        await loadCustomerList()
+      }
       if (props.detail.id) {
         let params = {
           customId: props.detail.id,

+ 12 - 8
pages/ib/components/pointDialog.vue

@@ -198,18 +198,16 @@
   }
 
   watch(() => props.detail, (val) => {
-    if (val) {
+    if (val.cId) {
       const { cId, id, comPoint1, hide1 } = val
       dialogForm.value = {
         cId, id, comPoint1, hide1,
       }
-
-      loadCommissionAccountTypes(val.ibId).then(
-        () => {
-          return setCommissionDefaultsFromLoginPoint(val.id)
-        },
-      )
-
+        loadCommissionAccountTypes(val.ibId).then(
+          () => {
+            return setCommissionDefaultsFromLoginPoint(val.id)
+          },
+        )
     }
   })
 
@@ -239,6 +237,11 @@
     dialogForm.value = {
       cId: '',
     }
+    commissionAccountTypeSettings.value = {
+      ecn: { selectedIndex: null, selectedItem: null, loginType: '2' },
+      standard: { selectedIndex: null, selectedItem: null, loginType: '7' },
+      cent: { selectedIndex: null, selectedItem: null, loginType: '8' },
+    }
     emit('close')
   }
 
@@ -280,6 +283,7 @@
     display: flex;
     align-items: center;
     margin-bottom: 20rpx;
+    font-size: px2rpx(20);
   }
 
   .account-adjust-cid-label {

+ 1 - 17
pages/ib/customer.vue

@@ -69,23 +69,6 @@
         :show-operation="true"
         :showPagination="true"
       >
-        <template #documentary="{ row }">
-          <button class="action-btn" @click.stop="handleMenuClick({type: 'documentary', row})">
-            {{ t('Documentary.AgentBackground.item1') }}
-          </button>
-        </template>
-        <template #applyIb="{ row }">
-          <button v-if="row.ibStatus == 1 && row.belongsType != 1" class="action-btn"
-                  @click.stop="handleMenuClick({type: 'applyIb', row})">{{ t('Home.msg.ibTitle') }}
-          </button>
-          <view v-else>--</view>
-        </template>
-        <template #Point="{ row }">
-          <button v-if="row.belongsType != 1" class="action-btn" @click.stop="handleMenuClick({type: 'Point', row})">
-            {{ t('Home.msg.ibTitle') }}
-          </button>
-          <view v-else>--</view>
-        </template>
       </cwg-tabel>
       <!-- 跟单全局设置     -->
       <DocumentaryDialog :visible="docVisible" :detail="formInfoRow" @close="closeDoc" @confirm="confirmDoc" />
@@ -345,6 +328,7 @@
     applyVisible.value = false
   }
   const closePoint = () => {
+    pointForm.value = {}
     pointVisible.value = false
   }
 

+ 2 - 3
pages/ib/report.vue

@@ -19,6 +19,7 @@
         :show-operation="false"
         :showPagination="true"
         :showSummary="true"
+        :immediate="false"
         :summaryMethod="getSummaries"
       >
       </cwg-tabel>
@@ -345,7 +346,7 @@
   // 接口 根据types 切换(动态代理不同类型报表API)
   const listApi = computed(() => {
     return async (params: any) => {
-      let apiFn: any
+      let apiFn = ibApi.tradeDw
       const type = search.value.reportType
       const detailType = search.value.detail_type
 
@@ -361,7 +362,6 @@
       else if (type == 6) apiFn = ibApi.ibReportBalance
       else if (type == 7) apiFn = ibApi.tradeSymbolCategory
       else if (type == 24) apiFn = ibApi.tradeSalesHidden
-
       if (apiFn) {
         if (type == 2) {
           const isVietnam = country.value === 'VN' // 这里需要你实际的取国家逻辑
@@ -369,7 +369,6 @@
         }
         return await apiFn(params)
       }
-      return Promise.reject('No API found')
     }
   })
 

+ 1 - 0
pages/ib/subsList.vue

@@ -251,6 +251,7 @@
     applyVisible.value = true
   }
   const closeApplyIb = () => {
+    applyDetail.value = {}
     applyVisible.value = false
   }
   const confirmApply = (data) => {