فهرست منبع

我的企业管理

“陈博论” 9 ماه پیش
والد
کامیت
5c5b034a0b

+ 2 - 2
web/src/views/frontend/Order.vue

@@ -180,8 +180,8 @@ const isEnterprise = computed(() => Number(goodsInfo.value?.type) === 2)
 const isSeatBasedEnterprise = computed(() => {
   if (!isEnterprise.value) return false
   const cardType = Number(goodsInfo.value?.card_type)
-  // 月卡(6)和年卡(7)不是按席位计算
-  return ![6, 7].includes(cardType)
+  // 企业套餐:次卡(1)、积分卡(2)、月卡(6)、年卡(7)按件数购买,其余按席位
+  return ![1, 2, 6, 7].includes(cardType)
 })
 
 // 获取卡类型文本

+ 11 - 36
web/src/views/frontend/components/AddMemberDialog.vue

@@ -4,12 +4,6 @@
     @close="handleClose">
     <div class="add-member-form">
       <div class="form-item">
-        <div class="label">
-          <el-icon class="label-icon">
-            <Message />
-          </el-icon>
-          <span>邮箱地址</span>
-        </div>
         <div class="input-with-button">
           <el-input v-model="form.email" placeholder="请输入邮箱地址" clearable :disabled="searching"
             @keyup.enter="handleSearch" @clear="handleClear">
@@ -78,11 +72,8 @@
 
     <template #footer>
       <span class="dialog-footer">
-        <el-button size="medium" @click="handleCancel">取消</el-button>
-        <el-button type="primary" size="medium" :loading="submitting" :disabled="!searchedUser" @click="handleSubmit">
-          <el-icon v-if="!submitting" class="submit-icon">
-            <Check />
-          </el-icon>
+        <el-button size="small" @click="handleCancel">取消</el-button>
+        <el-button type="primary" size="small" :loading="submitting" :disabled="!searchedUser" @click="handleSubmit">
           <span>{{ submitting ? '添加中...' : '确认添加' }}</span>
         </el-button>
       </span>
@@ -92,7 +83,7 @@
 
 <script setup>
 import { computed, reactive, ref, watch } from 'vue'
-import { Check, Message, Search, SuccessFilled, User, UserFilled, WarningFilled } from '@element-plus/icons-vue'
+import { Message, Search, SuccessFilled, User, UserFilled, WarningFilled } from '@element-plus/icons-vue'
 import { ElMessage } from 'element-plus'
 import { searchEmail, addMember } from '/@/api/frontend/me'
 
@@ -206,7 +197,6 @@ const handleSubmit = async () => {
     emit('success', searchedUser.value)
     emit('update:visible', false)
   } catch (error) {
-    console.error('添加成员失败:', error)
     ElMessage.error('添加成员失败,请稍后重试')
   } finally {
     submitting.value = false
@@ -261,7 +251,7 @@ const handleClose = () => {
 
   .el-dialog__header {
     padding: 20px 24px;
-    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    background: linear-gradient(135deg, #409eff 0%, #2563eb 100%);
     color: #fff;
     border-bottom: none;
 
@@ -318,7 +308,7 @@ const handleClose = () => {
     i {
       margin-right: 6px;
       font-size: 16px;
-      color: #667eea;
+      color: #409eff;
     }
 
     span {
@@ -338,22 +328,7 @@ const handleClose = () => {
         height: 40px;
         line-height: 40px;
         border-radius: 6px;
-        border: 1px solid #dcdfe6;
         transition: all 0.3s ease;
-
-        &:focus {
-          border-color: #667eea;
-          box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
-        }
-      }
-
-      ::v-deep .el-input__prefix {
-        left: 12px;
-        color: #909399;
-      }
-
-      ::v-deep .el-input__inner {
-        padding-left: 36px;
       }
     }
 
@@ -364,11 +339,11 @@ const handleClose = () => {
       border-radius: 6px;
       font-weight: 500;
       transition: all 0.3s ease;
-      box-shadow: 0 2px 4px rgba(102, 126, 234, 0.2);
+      box-shadow: 0 2px 4px rgba(64, 158, 255, 0.2);
 
       &:hover:not(:disabled) {
         transform: translateY(-1px);
-        box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
+        box-shadow: 0 4px 8px rgba(64, 158, 255, 0.3);
       }
 
       &:active:not(:disabled) {
@@ -413,13 +388,13 @@ const handleClose = () => {
       width: 48px;
       height: 48px;
       border-radius: 50%;
-      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      background: linear-gradient(135deg, #409eff 0%, #2563eb 100%);
       display: flex;
       align-items: center;
       justify-content: center;
       margin-right: 16px;
       flex-shrink: 0;
-      box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
+      box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
 
       i {
         font-size: 24px;
@@ -525,11 +500,11 @@ const handleClose = () => {
     transition: all 0.3s ease;
 
     &.el-button--primary {
-      box-shadow: 0 2px 4px rgba(102, 126, 234, 0.2);
+      box-shadow: 0 2px 4px rgba(64, 158, 255, 0.2);
 
       &:hover:not(:disabled) {
         transform: translateY(-1px);
-        box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
+        box-shadow: 0 4px 8px rgba(64, 158, 255, 0.3);
       }
 
       &:active:not(:disabled) {

+ 12 - 55
web/src/views/frontend/components/Me/enterprise.vue

@@ -46,12 +46,15 @@
           </div>
         </div>
 
+        <div style="margin-bottom: 10px;">
+          <el-button type="primary" size="small" plain @click="handleOpenProductSeatDialog">产品分配</el-button>
+        </div>
+
         <!-- 员工列表表格 -->
         <div class="table-section">
           <el-table ref="employeeTable" :data="employeeList" style="width: 100%"
             :header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: '500' }" empty-text="暂无员工数据"
-            @selection-change="handleSelectionChange">
-            <el-table-column type="selection" width="55" align="center" />
+           >
             <el-table-column prop="id" label="编号" width="100" align="center" />
             <el-table-column prop="name" label="姓名" min-width="150" align="center" />
             <el-table-column prop="email" label="邮箱" min-width="200" align="center" />
@@ -69,14 +72,9 @@
                 </el-tag>
               </template>
             </el-table-column>
-            <el-table-column prop="join_time_formatted" label="加入时间" width="160" align="center" />
-
-            <el-table-column label="操作" width="200" align="center" fixed="right">
+            <el-table-column prop="join_time" label="加入时间" width="160" align="center" />
+            <el-table-column label="操作" width="100" align="center" fixed="right">
               <template #default="{ row }">
-                <el-button type="primary" size="small" :disabled="!row.can_operate"
-                  @click="handleOpenProductSeatDialog([row.id])">
-                  产品分配
-                </el-button>
                 <el-button type="danger" size="small" :disabled="!row.can_operate || row.id === currentUserId"
                   @click="handleDeleteMember(row)">
                   删除
@@ -86,22 +84,6 @@
           </el-table>
         </div>
 
-        <!-- 批量操作工具栏 -->
-        <div v-if="selectedEmployeeIds.length > 0" class="batch-actions">
-          <div class="batch-info">
-            <span>已选择 <strong>{{ selectedEmployeeIds.length }}</strong> 位员工</span>
-          </div>
-          <div class="batch-buttons">
-            <el-button type="primary" size="small" :icon="Goods" :disabled="!canOperateSelected"
-              @click="handleOpenProductSeatDialog(selectedEmployeeIds)">
-              批量产品分配
-            </el-button>
-            <el-button size="small" :icon="Close" @click="handleClearSelection">
-              取消选择
-            </el-button>
-          </div>
-        </div>
-
         <!-- 分页控件 -->
         <div v-if="employeeList.length > 0 || total > 0" class="pagination-section">
           <el-pagination :current-page="currentPage" :page-size="pageSize" :page-sizes="pageSizes" :total="total"
@@ -169,7 +151,6 @@ const employeeList = ref<Employee[]>([
 const showCreateDialog = ref(false)
 const showAddMemberDialog = ref(false)
 const showProductSeatDialog = ref(false)
-const selectedEmployeeIds = ref<number[]>([])
 const selectedMemberIdsForDialog = ref<number[]>([])
 
 const employeeTable = ref()
@@ -199,13 +180,6 @@ const currentEnterpriseName = computed(() => {
 
 const currentUserId = computed(() => userId.value || null)
 
-// 检查选中的员工是否都可以操作
-const canOperateSelected = computed(() => {
-  if (selectedEmployeeIds.value.length === 0) return false
-  const selectedEmployees = employeeList.value.filter(emp => selectedEmployeeIds.value.includes(emp.id))
-  return selectedEmployees.every(emp => emp.can_operate)
-})
-
 // 请求锁,防止重复请求
 const isLoadingList = ref(false)
 
@@ -303,7 +277,7 @@ const handleDeleteMember = async (member: Employee) => {
       type: 'warning'
     })
 
-    const res = await removeMember({
+    await removeMember({
       member_id: member.id,
       company_id: companyId.value
     })
@@ -311,10 +285,7 @@ const handleDeleteMember = async (member: Employee) => {
     ElMessage.success('删除成员成功')
     loadEnterpriseList()
   } catch (error: any) {
-    if (error !== 'cancel') {
-      console.error('删除成员失败:', error)
-      ElMessage.error('删除成员失败')
-    }
+    ElMessage.error('删除成员失败')
   }
 }
 
@@ -343,29 +314,15 @@ const handleCheckCompany = async () => {
   }
 }
 
-const handleSelectionChange = (selection: Employee[]) => {
-  selectedEmployeeIds.value = selection.map(item => item.id)
-}
-
-// 分配产品席位
-const handleClearSelection = () => {
-  employeeTable.value?.clearSelection()
-  selectedEmployeeIds.value = []
-}
-
 // 选择分配员工
-const handleOpenProductSeatDialog = (memberIds: number[]) => {
-  if (!memberIds || memberIds.length === 0) {
-    ElMessage.warning('请至少选择一个员工')
-    return
-  }
-  selectedMemberIdsForDialog.value = memberIds
+const handleOpenProductSeatDialog = () => {
+  // 直接打开分配弹窗,不再依赖表格选中
+  selectedMemberIdsForDialog.value = []
   showProductSeatDialog.value = true
 }
 
 const handleProductSeatSuccess = (data: ProductSeatResult) => {
   loadEnterpriseList()
-  handleClearSelection()
   const count = data?.memberIds?.length || 0
   ElMessage.success(
     data.mode === 'assign'

+ 214 - 333
web/src/views/frontend/components/ProductSeatDialog.vue

@@ -1,7 +1,8 @@
 <template>
   <el-dialog
     title="产品分配席位"
-    v-model:visible="dialogVisible"
+    :model-value="props.visible"
+    @update:model-value="(val) => emit('update:visible', val)"
     width="900px"
     :close-on-click-modal="false"
     :close-on-press-escape="true"
@@ -36,84 +37,23 @@
         </div>
       </div>
 
-      <!-- 选择人员 - 卡片式布局 -->
-      <div class="member-section">
-        <div class="section-header">
-          <div class="section-title">
-            <el-icon>
-              <UserFilled />
-            </el-icon>
-            <span>选择人员</span>
-          </div>
-          <div class="section-actions">
-            <el-button type="text" size="small" :icon="Check" @click="handleSelectAll">
-              全选
-            </el-button>
-            <el-button type="text" size="small" :icon="Close" @click="handleSelectNone">
-              清空
-            </el-button>
-          </div>
-        </div>
-        <div class="member-list-wrapper">
-          <div v-if="selectedMembers.length > 0" class="selected-badge">
-            <el-icon>
-              <Check />
-            </el-icon>
-            <span>已选择 <strong>{{ selectedMembers.length }}</strong> 位成员</span>
-          </div>
-          <div class="member-list">
-            <el-checkbox-group v-model="selectedMembers" @change="handleMemberChange">
-              <div
-                v-for="member in memberList"
-                :key="member.id"
-                class="member-card"
-                :class="{ selected: selectedMembers.includes(member.id) }"
-              >
-                <el-checkbox :label="member.id" class="member-checkbox">
-                  <div class="member-avatar">
-                    <el-icon>
-                      <UserFilled />
-                    </el-icon>
-                  </div>
-                  <div class="member-details">
-                    <div class="member-name">{{ member.name }}</div>
-                    <div class="member-email">{{ member.email }}</div>
-                  </div>
-                  <div v-if="selectedMembers.includes(member.id)" class="member-check-icon">
-                    <el-icon>
-                      <Check />
-                    </el-icon>
-                  </div>
-                </el-checkbox>
-              </div>
-            </el-checkbox-group>
-            <div v-if="memberList.length === 0" class="empty-member">
-              <el-icon>
-                <User />
-              </el-icon>
-              <p>暂无人员数据</p>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <!-- 选择产品 - 卡片式展示 -->
-      <div v-if="operationMode === 'assign'" class="product-section">
+      <!-- 选择套餐(分配/取消统一放在最前,进入即展示) -->
+      <div class="product-section">
         <div class="section-header">
           <div class="section-title">
             <el-icon>
               <Goods />
             </el-icon>
-            <span>选择套餐产品</span>
+            <span>{{ operationMode === 'assign' ? '选择可分配套餐' : '选择要取消的套餐' }}</span>
           </div>
         </div>
         <div class="product-grid">
           <div
-            v-for="product in availableProducts"
+            v-for="product in currentProductList"
             :key="product.id"
             class="product-card"
             :class="{ active: selectedProduct === product.id }"
-            @click="selectedProduct = product.id"
+            @click="handleProductSelect(product.id)"
           >
             <div class="product-image">
               <img :src="getProductImage(product.id)" :alt="product.name">
@@ -121,12 +61,14 @@
             <div class="product-content">
               <div class="product-name">{{ product.name }}</div>
               <div v-if="product.description" class="product-desc">{{ product.description }}</div>
-              <div v-if="product.seats !== undefined" class="product-meta">
+              <div class="product-meta">
                 <span class="seats-info">
                   <el-icon>
-                    <User />
+                    <UserFilled />
                   </el-icon>
-                  <span>可用席位: {{ product.seats }}</span>
+                  <span>
+                    {{ operationMode === 'assign' ? `可用席位: ${product.seats ?? 0}` : `已分配 ${product.assignedCount || 0} 人` }}
+                  </span>
                 </span>
               </div>
             </div>
@@ -136,81 +78,95 @@
               </el-icon>
             </div>
           </div>
-          <div v-if="availableProducts.length === 0" class="empty-product">
+          <div v-if="currentProductList.length === 0" class="empty-product">
             <el-icon>
               <Goods />
             </el-icon>
-            <p>暂无可用产品</p>
+            <p>{{ operationMode === 'assign' ? '没有可以分配的套餐' : '暂无可取消的套餐' }}</p>
           </div>
         </div>
       </div>
 
-      <!-- 已分配产品列表(取消分配模式) -->
-      <div v-if="operationMode === 'cancel'" class="assigned-section">
+      <!-- 选择人员 - 卡片式布局 -->
+      <div class="member-section">
         <div class="section-header">
           <div class="section-title">
             <el-icon>
-              <List />
+              <UserFilled />
             </el-icon>
-            <span>已分配产品</span>
+            <span>{{ operationMode === 'assign' ? '选择人员' : '选择要取消分配的人员' }}</span>
+          </div>
+          <div class="section-actions">
+            <el-button type="text" size="small" :icon="Check" @click="handleSelectAll" :disabled="!selectedProduct">
+              全选
+            </el-button>
+            <el-button type="text" size="small" :icon="Close" @click="handleSelectNone">
+              清空
+            </el-button>
           </div>
         </div>
-        <div v-if="assignedProducts.length > 0" class="assigned-product-list">
-          <div
-            v-for="product in assignedProducts"
-            :key="product.id"
-            class="assigned-product-card"
-            :class="{ active: selectedProduct === product.id }"
-            @click="selectedProduct = product.id"
-          >
-            <div class="product-icon assigned">
-              <el-icon>
-                <Goods />
-              </el-icon>
-            </div>
-            <div class="product-content">
-              <div class="product-name">{{ product.name }}</div>
-              <div v-if="product.description" class="product-desc">{{ product.description }}</div>
-              <div class="product-meta">
-                <span class="assigned-count">
-                  <el-icon>
-                    <UserFilled />
-                  </el-icon>
-                  <span>已分配 {{ product.assignedCount || selectedMembers.length }} 人</span>
-                </span>
+        <div class="member-list-wrapper">
+          <div v-if="selectedProduct && selectedMembers.length > 0" class="selected-badge">
+            <el-icon>
+              <Check />
+            </el-icon>
+            <span>已选择 <strong>{{ selectedMembers.length }}</strong> 位成员</span>
+          </div>
+          <div class="member-list-scroll">
+            <template v-if="selectedProduct">
+              <el-checkbox-group v-model="selectedMembers" @change="handleMemberChange">
+                <div
+                  v-for="member in currentMemberList"
+                  :key="member.id"
+                  class="member-card"
+                  :class="{ selected: selectedMembers.includes(member.id) }"
+                >
+                  <el-checkbox :label="member.id" class="member-checkbox">
+                    <div class="member-avatar">
+                      <el-icon>
+                        <UserFilled />
+                      </el-icon>
+                    </div>
+                    <div class="member-details">
+                      <div class="member-name">{{ member.name }}</div>
+                      <div class="member-email">{{ member.email }}</div>
+                    </div>
+                    <div v-if="selectedMembers.includes(member.id)" class="member-check-icon">
+                      <el-icon>
+                        <Check />
+                      </el-icon>
+                    </div>
+                  </el-checkbox>
+                </div>
+              </el-checkbox-group>
+              <div v-if="currentMemberList.length === 0" class="empty-member">
+                <el-icon>
+                  <User />
+                </el-icon>
+                <p>{{ operationMode === 'assign' ? '暂无可分配的人员' : '该套餐暂无可取消的人员' }}</p>
               </div>
-            </div>
-            <div v-if="selectedProduct === product.id" class="product-check">
+            </template>
+            <div v-else class="empty-member">
               <el-icon>
-                <Check />
+                <User />
               </el-icon>
+              <p>请选择套餐后查看可{{ operationMode === 'assign' ? '分配' : '取消' }}的人员</p>
             </div>
           </div>
         </div>
-        <div v-else-if="selectedMembers.length > 0" class="empty-assigned">
-          <el-icon>
-            <InfoFilled />
-          </el-icon>
-          <p>选中人员暂无已分配的产品</p>
-        </div>
-        <div v-else class="empty-assigned">
-          <el-icon>
-            <InfoFilled />
-          </el-icon>
-          <p>请先选择要操作的人员</p>
-        </div>
       </div>
+
     </div>
 
     <template #footer>
       <span class="dialog-footer">
-        <el-button size="medium" @click="handleCancel">
+        <el-button size="small" @click="handleCancel">
           <el-icon>
             <Close />
           </el-icon>
           <span>取消</span>
         </el-button>
-        <el-button type="primary" size="medium" :loading="submitting" :disabled="!canSubmit" @click="handleSubmit">
+        <el-button type="primary" size="small" :loading="submitting" :disabled="!canSubmit" @click="handleSubmit">
           <el-icon v-if="!submitting">
             <component :is="operationMode === 'assign' ? Check : Remove" />
           </el-icon>
@@ -223,7 +179,7 @@
 
 <script setup>
 import { computed, getCurrentInstance, ref, watch } from 'vue'
-import { Check, Close, Goods, InfoFilled, List, Minus, Plus, Remove, User, UserFilled } from '@element-plus/icons-vue'
+import { Check, Close, Goods, Minus, Plus, Remove, User, UserFilled } from '@element-plus/icons-vue'
 
 defineOptions({ name: 'ProductSeatDialog' })
 
@@ -251,18 +207,28 @@ const selectedProduct = ref(null)
 const submitting = ref(false)
 const availableProducts = ref([])
 const assignedProducts = ref([])
+const productMembersMap = ref({}) // 存储每个产品下的人员列表
+const currentProductList = computed(() =>
+  operationMode.value === 'assign' ? availableProducts.value : assignedProducts.value
+)
 
-const dialogVisible = computed({
-  get: () => props.visible,
-  set: val => emit('update:visible', val)
+// 当前显示的人员列表
+const currentMemberList = computed(() => {
+  if (!selectedProduct.value) return []
+  if (operationMode.value === 'assign') {
+    return props.memberList
+  }
+  return productMembersMap.value[selectedProduct.value] || []
 })
 
 const canSubmit = computed(() => {
-  if (selectedMembers.value.length === 0) return false
   if (operationMode.value === 'assign') {
-    return !!selectedProduct.value
+    // 分配模式:需要选择人员且选择产品
+    return selectedMembers.value.length > 0 && !!selectedProduct.value
+  } else {
+    // 取消分配模式:需要选择产品且选择人员
+    return !!selectedProduct.value && selectedMembers.value.length > 0
   }
-  return assignedProducts.value.length > 0
 })
 
 const message = proxy?.$message
@@ -288,31 +254,11 @@ watch(
   { immediate: true, deep: true }
 )
 
-watch(operationMode, newVal => {
-  if (newVal === 'cancel' && selectedMembers.value.length > 0) {
-    loadAssignedProducts()
-  } else {
-    assignedProducts.value = []
-  }
-  selectedProduct.value = null
-})
-
-watch(
-  selectedMembers,
-  newVal => {
-    if (newVal.length > 0 && operationMode.value === 'cancel') {
-      loadAssignedProducts()
-    } else if (newVal.length === 0) {
-      assignedProducts.value = []
-    }
-  },
-  { deep: true }
-)
-
 const initDialog = async () => {
   selectedMembers.value = props.selectedMemberIds.length > 0 ? [...props.selectedMemberIds] : []
-  await loadAvailableProducts()
-  if (operationMode.value === 'cancel' && selectedMembers.value.length > 0) {
+  if (operationMode.value === 'assign') {
+    await loadAvailableProducts()
+  } else {
     await loadAssignedProducts()
   }
 }
@@ -333,60 +279,82 @@ const loadAvailableProducts = async () => {
         seats: 5
       }
     ]
+    autoSelectFirstProduct()
   } catch (error) {
     console.error('加载产品列表失败:', error)
     message?.error('加载产品列表失败')
     availableProducts.value = []
+    selectedProduct.value = null
   }
 }
 
 const loadAssignedProducts = async () => {
-  if (selectedMembers.value.length === 0) {
-    assignedProducts.value = []
-    selectedProduct.value = null
-    return
-  }
-
   try {
+    // 加载所有已分配的产品列表
     assignedProducts.value = [
       {
         id: 1,
         name: '聚德神笔套餐',
         description: '聚德 AI 写作/办公套件',
-        assignedCount: selectedMembers.value.length
+        assignedCount: 2
       }
     ]
 
-    if (assignedProducts.value.length === 1 && !selectedProduct.value) {
-      selectedProduct.value = assignedProducts.value[0].id
-    }
+    // 加载每个产品下的人员列表
+    // 这里应该调用API获取每个产品下的人员
+    // 暂时使用模拟数据
+    assignedProducts.value.forEach(product => {
+      // 模拟数据:每个产品下的人员
+      productMembersMap.value[product.id] = props.memberList.filter((member, index) => {
+        // 模拟:产品1包含前2个成员
+        if (product.id === 1) {
+          return index < 2
+        }
+        return false
+      })
+    })
+    autoSelectFirstProduct()
   } catch (error) {
     console.error('加载已分配产品失败:', error)
     message?.error('加载已分配产品失败')
     assignedProducts.value = []
+    productMembersMap.value = {}
+    selectedProduct.value = null
+  }
+}
+
+const autoSelectFirstProduct = () => {
+  if (currentProductList.value.length > 0) {
+    selectedProduct.value = currentProductList.value[0].id
+  } else {
     selectedProduct.value = null
   }
+  selectedMembers.value = []
 }
 
-const handleModeChange = () => {
+// 选择产品时,更新人员列表
+const handleProductSelect = productId => {
+  selectedProduct.value = productId
+  selectedMembers.value = []
+}
+
+const handleModeChange = async () => {
+  // 切换模式时重置状态并重新拉取对应产品列表
   selectedProduct.value = null
-  if (operationMode.value === 'cancel' && selectedMembers.value.length > 0) {
-    loadAssignedProducts()
+  selectedMembers.value = []
+  if (operationMode.value === 'assign') {
+    await loadAvailableProducts()
   } else {
-    assignedProducts.value = []
+    await loadAssignedProducts()
   }
 }
 
 const handleMemberChange = () => {
-  if (operationMode.value === 'cancel' && selectedMembers.value.length > 0) {
-    loadAssignedProducts()
-  } else if (selectedMembers.value.length === 0) {
-    assignedProducts.value = []
-  }
+  // 人员选择变化时的处理
 }
 
 const handleSelectAll = () => {
-  selectedMembers.value = props.memberList.map(m => m.id)
+  selectedMembers.value = currentMemberList.value.map(m => m.id)
 }
 
 const handleSelectNone = () => {
@@ -401,18 +369,18 @@ const handleCancelProduct = product => {
 }
 
 const handleSubmit = async () => {
+  if (!selectedProduct.value) {
+    message?.warning(operationMode.value === 'assign' ? '没有可分配的套餐' : '请先选择要取消的套餐')
+    return
+  }
+
   if (selectedMembers.value.length === 0) {
     message?.warning('请至少选择一个人员')
     return
   }
 
-  if (operationMode.value === 'assign') {
-    if (!selectedProduct.value) {
-      message?.warning('请选择要分配的套餐产品')
-      return
-    }
-  } else if (!selectedProduct.value && assignedProducts.value.length === 0) {
-    message?.warning('没有可取消分配的产品')
+  if (operationMode.value !== 'assign' && selectedMembers.value.length === 0) {
+    message?.warning('请选择要取消分配的人员')
     return
   }
 
@@ -432,7 +400,7 @@ const handleSubmit = async () => {
       productId,
       memberIds: selectedMembers.value
     })
-    dialogVisible.value = false
+    emit('update:visible', false)
   } catch (error) {
     console.error('操作失败:', error)
     message?.error(operationMode.value === 'assign' ? '分配席位失败' : '取消分配失败')
@@ -442,7 +410,7 @@ const handleSubmit = async () => {
 }
 
 const handleCancel = () => {
-  dialogVisible.value = false
+  emit('update:visible', false)
 }
 
 const handleClose = () => {
@@ -455,6 +423,7 @@ const resetForm = () => {
   selectedMembers.value = []
   selectedProduct.value = null
   assignedProducts.value = []
+  productMembersMap.value = {}
   submitting.value = false
 }
 </script>
@@ -619,59 +588,73 @@ const resetForm = () => {
         }
       }
 
-      .member-list {
-        max-height: 300px;
+      .member-list-scroll {
+        max-height: 320px;
         overflow-y: auto;
-        padding: 4px;
+        overflow-x: hidden;
+        padding: 8px;
         border-radius: 12px;
+        background: #fafbfc;
+        border: 1px solid #f0f2f5;
 
+        // 自定义滚动条样式
         &::-webkit-scrollbar {
-          width: 6px;
+          width: 8px;
         }
 
         &::-webkit-scrollbar-track {
           background: #f5f7fa;
-          border-radius: 3px;
+          border-radius: 4px;
+          margin: 4px 0;
         }
 
         &::-webkit-scrollbar-thumb {
-          background: #dcdfe6;
-          border-radius: 3px;
+          background: linear-gradient(180deg, #c0c4cc 0%, #909399 100%);
+          border-radius: 4px;
+          transition: background 0.3s ease;
 
           &:hover {
-            background: #c0c4cc;
+            background: linear-gradient(180deg, #909399 0%, #606266 100%);
           }
         }
 
+        // Firefox 滚动条样式
+        scrollbar-width: thin;
+        scrollbar-color: #c0c4cc #f5f7fa;
+
+        // 复选框组样式
+        ::v-deep .el-checkbox-group {
+          display: flex;
+          flex-direction: column;
+          gap: 10px;
+        }
+
         .member-card {
           position: relative;
-          margin-bottom: 12px;
-          border-radius: 12px;
-          border: 2px solid #f0f2f5;
+          border-radius: 10px;
+          border: 1.5px solid #e4e7ed;
           background: #fff;
-          transition: all 0.3s ease;
+          transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
           overflow: hidden;
-
-          &:last-child {
-            margin-bottom: 0;
-          }
+          box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
 
           &:hover {
-            border-color: #b3d8ff;
-            box-shadow: 0 4px 12px rgba(64, 158, 255, 0.1);
-            transform: translateY(-2px);
+            border-color: #409eff;
+            box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
+            transform: translateY(-1px);
           }
 
           &.selected {
             border-color: #409eff;
-            background: linear-gradient(135deg, #ecf5ff 0%, #e1f3ff 100%);
-            box-shadow: 0 4px 16px rgba(64, 158, 255, 0.2);
+            background: linear-gradient(135deg, #ecf5ff 0%, #d9ecff 100%);
+            box-shadow: 0 4px 16px rgba(64, 158, 255, 0.25);
+            transform: translateY(-1px);
           }
 
           ::v-deep .member-checkbox {
             width: 100%;
             margin: 0;
-            padding: 16px;
+            padding: 30px 14px;
             display: flex;
             align-items: center;
             cursor: pointer;
@@ -685,59 +668,74 @@ const resetForm = () => {
               padding: 0;
               display: flex;
               align-items: center;
+              gap: 12px;
             }
           }
 
           .member-avatar {
-            width: 48px;
-            height: 48px;
-            border-radius: 12px;
+            width: 44px;
+            height: 44px;
+            border-radius: 10px;
             background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
             display: flex;
             align-items: center;
             justify-content: center;
-            margin-right: 16px;
             flex-shrink: 0;
-            box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+            box-shadow: 0 2px 6px rgba(64, 158, 255, 0.25);
+            transition: transform 0.2s ease;
 
             i {
-              font-size: 24px;
+              font-size: 22px;
               color: #fff;
             }
           }
 
+          .member-card:hover .member-avatar {
+            transform: scale(1.05);
+          }
+
+          .member-card.selected .member-avatar {
+            box-shadow: 0 3px 10px rgba(64, 158, 255, 0.4);
+          }
+
           .member-details {
             flex: 1;
             min-width: 0;
+            overflow: hidden;
 
             .member-name {
               font-size: 15px;
               font-weight: 600;
               color: #303133;
               margin-bottom: 4px;
+              white-space: nowrap;
+              overflow: hidden;
+              text-overflow: ellipsis;
             }
 
             .member-email {
               font-size: 13px;
               color: #909399;
-              word-break: break-all;
+              white-space: nowrap;
+              overflow: hidden;
+              text-overflow: ellipsis;
             }
           }
 
           .member-check-icon {
-            width: 32px;
-            height: 32px;
+            width: 28px;
+            height: 28px;
             border-radius: 50%;
             background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
             display: flex;
             align-items: center;
             justify-content: center;
-            margin-left: 12px;
             flex-shrink: 0;
-            animation: scaleIn 0.3s ease;
+            box-shadow: 0 2px 6px rgba(103, 194, 58, 0.3);
+            animation: scaleIn 0.25s cubic-bezier(0.4, 0, 0.2, 1);
 
             i {
-              font-size: 18px;
+              font-size: 16px;
               color: #fff;
             }
           }
@@ -748,17 +746,22 @@ const resetForm = () => {
           flex-direction: column;
           align-items: center;
           justify-content: center;
-          padding: 60px 20px;
+          padding: 50px 20px;
           color: #c0c4cc;
+          background: #fafbfc;
+          border-radius: 8px;
+          border: 1px dashed #e4e7ed;
 
           i {
             font-size: 48px;
             margin-bottom: 12px;
+            opacity: 0.6;
           }
 
           p {
             margin: 0;
             font-size: 14px;
+            color: #909399;
           }
         }
       }
@@ -932,129 +935,6 @@ const resetForm = () => {
     }
   }
 
-  // 已分配产品区域
-  .assigned-section {
-    .assigned-product-list {
-      display: flex;
-      flex-direction: column;
-      gap: 12px;
-
-      .assigned-product-card {
-        position: relative;
-        display: flex;
-        align-items: center;
-        padding: 16px 20px;
-        border-radius: 12px;
-        border: 2px solid #f0f2f5;
-        background: #fff;
-        cursor: pointer;
-        transition: all 0.3s ease;
-
-        &:hover {
-          border-color: #f56c6c;
-          box-shadow: 0 4px 12px rgba(245, 108, 108, 0.1);
-          transform: translateX(4px);
-        }
-
-        &.active {
-          border-color: #f56c6c;
-          background: linear-gradient(135deg, #fef0f0 0%, #fde2e2 100%);
-          box-shadow: 0 4px 16px rgba(245, 108, 108, 0.2);
-        }
-
-        .product-icon {
-          width: 48px;
-          height: 48px;
-          border-radius: 10px;
-          background: linear-gradient(135deg, #f56c6c 0%, #f78989 100%);
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          margin-right: 16px;
-          flex-shrink: 0;
-          box-shadow: 0 2px 8px rgba(245, 108, 108, 0.3);
-
-          i {
-            font-size: 24px;
-            color: #fff;
-          }
-        }
-
-        .product-content {
-          flex: 1;
-          min-width: 0;
-
-          .product-name {
-            font-size: 15px;
-            font-weight: 600;
-            color: #303133;
-            margin-bottom: 4px;
-          }
-
-          .product-desc {
-            font-size: 13px;
-            color: #909399;
-            margin-bottom: 8px;
-          }
-
-          .product-meta {
-            .assigned-count {
-              display: flex;
-              align-items: center;
-              gap: 4px;
-              font-size: 13px;
-              color: #f56c6c;
-              font-weight: 500;
-
-              i {
-                font-size: 14px;
-              }
-            }
-          }
-        }
-
-        .product-check {
-          width: 28px;
-          height: 28px;
-          border-radius: 50%;
-          background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          flex-shrink: 0;
-          box-shadow: 0 2px 8px rgba(103, 194, 58, 0.3);
-          animation: scaleIn 0.3s ease;
-
-          i {
-            font-size: 16px;
-            color: #fff;
-          }
-        }
-      }
-    }
-
-    .empty-assigned {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      justify-content: center;
-      padding: 60px 20px;
-      color: #c0c4cc;
-      background: #fafafa;
-      border-radius: 12px;
-      border: 2px dashed #e4e7ed;
-
-      i {
-        font-size: 48px;
-        margin-bottom: 12px;
-      }
-
-      p {
-        margin: 0;
-        font-size: 14px;
-      }
-    }
-  }
 }
 
 @keyframes scaleIn {
@@ -1108,3 +988,4 @@ const resetForm = () => {
   }
 }
 </style>
+