Просмотр исходного кода

chore: auto bump version and changelog [skip ci]

iwt 3 дней назад
Родитель
Сommit
02b7333fb0

+ 6 - 0
cfc-frontend/pages.json

@@ -1815,6 +1815,12 @@
             "navigationBarTitleText": "服务角色申请"
           }
         },
+        {
+          "path": "team-system/team-system",
+          "style": {
+            "navigationBarTitleText": "我的团队"
+          }
+        },
         {
           "path": "message-history",
           "style": {

+ 616 - 0
cfc-frontend/pages/vendor/team-system/team-system.vue

@@ -0,0 +1,616 @@
+<template>
+  <view class="page-team-system">
+    <!-- 头部:体系信息 -->
+    <view class="system-header" v-if="systemInfo">
+      <view class="system-info">
+        <view class="system-icon">🏢</view>
+        <view class="system-detail">
+          <text class="system-name">{{ systemInfo.name || '我的供应商体系' }}</text>
+          <text class="system-desc">服务商团队管理</text>
+        </view>
+      </view>
+      <view class="system-status" :class="systemInfo.status">
+        <text>{{ systemInfo.status === 'active' ? '活跃' : '未激活' }}</text>
+      </view>
+    </view>
+
+    <!-- 层级关系 -->
+    <view class="section" v-if="hierarchyInfo">
+      <view class="section-header">
+        <text class="section-title">层级关系</text>
+      </view>
+      <view class="hierarchy-grid">
+        <!-- 上级 -->
+        <view class="hierarchy-item" v-if="hierarchyInfo.parent">
+          <view class="hierarchy-label">上级服务商</view>
+          <view class="hierarchy-person">
+            <image v-if="hierarchyInfo.parent.avatar" class="avatar-img" :src="hierarchyInfo.parent.avatar" mode="aspectFill"></image>
+            <text v-else class="avatar-placeholder">👤</text>
+            <view class="person-info">
+              <text class="person-name">{{ hierarchyInfo.parent.nickname || '服务商' }}</text>
+              <text class="person-level">上级</text>
+            </view>
+          </view>
+        </view>
+
+        <!-- 下级 -->
+        <view class="hierarchy-item" v-else>
+          <view class="hierarchy-label">下级服务商</view>
+          <view class="hierarchy-empty">
+            <text>暂无下级</text>
+          </view>
+        </view>
+
+        <!-- 团队规模 -->
+        <view class="hierarchy-stat">
+          <text class="stat-value">{{ hierarchyInfo.totalChildren || 0 }}</text>
+          <text class="stat-label">团队成员</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 团队统计 -->
+    <view class="section">
+      <view class="section-header">
+        <text class="section-title">团队数据</text>
+      </view>
+      <view class="stats-grid">
+        <view class="stat-card">
+          <text class="stat-num">{{ teamStats.memberCount || 0 }}</text>
+          <text class="stat-name">团队成员</text>
+        </view>
+        <view class="stat-card">
+          <text class="stat-num">{{ teamStats.customerCount || 0 }}</text>
+          <text class="stat-name">客户归属</text>
+        </view>
+        <view class="stat-card">
+          <text class="stat-num">{{ teamStats.orderCount || 0 }}</text>
+          <text class="stat-name">关联订单</text>
+        </view>
+        <view class="stat-card">
+          <text class="stat-num">{{ teamStats.amount || 0 }}</text>
+          <text class="stat-name">关联金额(元)</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 团队成员列表 -->
+    <view class="section">
+      <view class="section-header">
+        <text class="section-title">团队成员</text>
+        <text class="section-action" @click="goToMembers">查看全部</text>
+      </view>
+      <view class="member-list">
+        <view class="member-item" v-for="member in teamMembers.slice(0, 5)" :key="member.userId">
+          <image v-if="member.avatar" class="member-avatar" :src="member.avatar" mode="aspectFill"></image>
+          <text v-else class="member-avatar-placeholder">👤</text>
+          <view class="member-info">
+            <text class="member-name">{{ member.nickname || '微信用户' }}</text>
+            <text class="member-role">{{ member.role || '成员' }}</text>
+          </view>
+          <view class="member-stats">
+            <text class="member-count">{{ member.customerCount || 0 }}</text>
+            <text class="member-label">客户</text>
+          </view>
+        </view>
+        <view class="empty-tip" v-if="teamMembers.length === 0">
+          <text>暂无团队成员</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 团队客户列表 -->
+    <view class="section">
+      <view class="section-header">
+        <text class="section-title">团队客户</text>
+        <text class="section-action" @click="goToCustomers">查看全部</text>
+      </view>
+      <view class="customer-list">
+        <view class="customer-item" v-for="customer in teamCustomers.slice(0, 5)" :key="customer.id">
+          <view class="customer-avatar">
+            <text class="customer-icon">👥</text>
+          </view>
+          <view class="customer-info">
+            <text class="customer-name">{{ customer.familyName || '未知家庭' }}</text>
+            <text class="customer-meta">{{ customer.source === 'manual' ? '人工绑定' : customer.source === 'order' ? '下单自动' : '推荐码' }}</text>
+          </view>
+          <view class="customer-stats">
+            <text class="customer-orders">{{ customer.orderCount || 0 }}</text>
+            <text class="customer-label">订单</text>
+          </view>
+        </view>
+        <view class="empty-tip" v-if="teamCustomers.length === 0">
+          <text>暂无客户数据</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 操作按钮 -->
+    <view class="action-bar">
+      <button class="btn-manage" @click="goToManage">团队管理</button>
+      <button class="btn-customers" @click="goToCustomers">客户列表</button>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      systemInfo: null,
+      hierarchyInfo: null,
+      teamStats: {
+        memberCount: 0,
+        customerCount: 0,
+        orderCount: 0,
+        amount: 0
+      },
+      teamMembers: [],
+      teamCustomers: []
+    }
+  },
+
+  onLoad() {
+    this.loadData()
+  },
+
+  onShow() {
+    this.loadData()
+  },
+
+  methods: {
+    async loadData() {
+      try {
+        // 并发获取所有数据
+        const [systemRes, hierarchyRes, membersRes, customersRes] = await Promise.all([
+          this.fetchSystemInfo(),
+          this.fetchHierarchyInfo(),
+          this.fetchTeamMembers(),
+          this.fetchTeamCustomers()
+        ])
+
+        if (systemRes) {
+          this.systemInfo = systemRes
+        }
+        if (hierarchyRes) {
+          this.hierarchyInfo = hierarchyRes
+        }
+        if (membersRes) {
+          this.teamMembers = membersRes
+        }
+        if (customersRes) {
+          this.teamCustomers = customersRes
+        }
+
+        // 更新统计
+        this.updateStats()
+      } catch (error) {
+        console.error('加载团队数据失败:', error)
+      }
+    },
+
+    // 获取体系信息
+    async fetchSystemInfo() {
+      try {
+        const res = await this.$http.get('/api/admin/supply-system/my-system', {})
+        return res.data || null
+      } catch (e) {
+        return null
+      }
+    },
+
+    // 获取层级关系
+    async fetchHierarchyInfo() {
+      try {
+        const res = await this.$http.get('/api/admin/supply-hierarchy/my', {})
+        return res.data || null
+      } catch (e) {
+        return null
+      }
+    },
+
+    // 获取团队成员
+    async fetchTeamMembers() {
+      try {
+        const res = await this.$http.get('/api/admin/supply-team/members', {
+          systemId: this.systemInfo?.id
+        })
+        return res.data || []
+      } catch (e) {
+        return []
+      }
+    },
+
+    // 获取团队客户
+    async fetchTeamCustomers() {
+      try {
+        const res = await this.$http.get('/api/admin/supply-team/customers', {
+          systemId: this.systemInfo?.id,
+          parentId: null,
+          page: 1,
+          size: 50
+        })
+        return res.data || []
+      } catch (e) {
+        return []
+      }
+    },
+
+    // 更新统计数据
+    updateStats() {
+      this.teamStats.memberCount = this.teamMembers.length
+      this.teamStats.customerCount = this.teamCustomers.length
+    },
+
+    // 跳转管理页面
+    goToManage() {
+      uni.navigateTo({
+        url: '/pages/vendor/team-manage/team-manage'
+      })
+    },
+
+    // 跳转客户列表
+    goToCustomers() {
+      uni.navigateTo({
+        url: '/pages/vendor/team-customers/team-customers'
+      })
+    }
+  }
+}
+</script>
+
+<style>
+.page-team-system {
+  background-color: #FFF7ED;
+  min-height: 100vh;
+  padding-bottom: 100rpx;
+}
+
+/* 体系头部 */
+.system-header {
+  background-color: #fff;
+  padding: 30rpx;
+  margin-bottom: 24rpx;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
+}
+
+.system-info {
+  display: flex;
+  align-items: center;
+}
+
+.system-icon {
+  font-size: 64rpx;
+  margin-right: 20rpx;
+}
+
+.system-name {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.system-desc {
+  font-size: 24rpx;
+  color: #94A3B8;
+  margin-top: 4rpx;
+}
+
+.system-status {
+  padding: 8rpx 20rpx;
+  border-radius: 20rpx;
+  font-size: 24rpx;
+}
+
+.system-status.active {
+  background-color: #E6F7EE;
+  color: #65C29A;
+}
+
+.system-status.inactive {
+  background-color: #FFF7EE;
+  color: #F97316;
+}
+
+/* 区块 */
+.section {
+  background-color: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  margin: 0 30rpx 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
+}
+
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24rpx;
+}
+
+.section-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.section-action {
+  font-size: 24rpx;
+  color: #F97316;
+}
+
+/* 层级关系 */
+.hierarchy-grid {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.hierarchy-item {
+  flex: 1;
+  min-width: 48%;
+  padding: 20rpx;
+  background-color: #FFF7ED;
+  border-radius: 12rpx;
+  margin-bottom: 16rpx;
+}
+
+.hierarchy-label {
+  font-size: 24rpx;
+  color: #666;
+  margin-bottom: 12rpx;
+}
+
+.hierarchy-person {
+  display: flex;
+  align-items: center;
+}
+
+.avatar-img {
+  width: 64rpx;
+  height: 64rpx;
+  border-radius: 50%;
+  margin-right: 16rpx;
+}
+
+.avatar-placeholder {
+  width: 64rpx;
+  height: 64rpx;
+  border-radius: 50%;
+  background-color: #F5FAFE;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 32rpx;
+  margin-right: 16rpx;
+}
+
+.person-info {
+  flex: 1;
+}
+
+.person-name {
+  font-size: 26rpx;
+  color: #333;
+  display: block;
+}
+
+.person-level {
+  font-size: 20rpx;
+  color: #94A3B8;
+}
+
+.hierarchy-empty {
+  font-size: 24rpx;
+  color: #94A3B8;
+}
+
+.hierarchy-stat {
+  flex: 1;
+  min-width: 48%;
+  padding: 20rpx;
+  background-color: #F5FAFE;
+  border-radius: 12rpx;
+  text-align: center;
+}
+
+.stat-value {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #2F5496;
+  display: block;
+}
+
+.stat-label {
+  font-size: 22rpx;
+  color: #666;
+}
+
+/* 统计网格 */
+.stats-grid {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.stat-card {
+  width: 50%;
+  padding: 20rpx;
+  text-align: center;
+  border-right: 1rpx solid #FED7AA;
+  border-bottom: 1rpx solid #FED7AA;
+}
+
+.stat-num {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #F97316;
+  display: block;
+}
+
+.stat-name {
+  font-size: 22rpx;
+  color: #666;
+}
+
+/* 成员列表 */
+.member-item {
+  display: flex;
+  align-items: center;
+  padding: 16rpx 0;
+  border-bottom: 1rpx solid #FED7AA;
+}
+
+.member-item:last-child {
+  border-bottom: none;
+}
+
+.member-avatar {
+  width: 64rpx;
+  height: 64rpx;
+  border-radius: 50%;
+  margin-right: 16rpx;
+}
+
+.member-avatar-placeholder {
+  width: 64rpx;
+  height: 64rpx;
+  border-radius: 50%;
+  background-color: #F5FAFE;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 32rpx;
+  margin-right: 16rpx;
+}
+
+.member-info {
+  flex: 1;
+}
+
+.member-name {
+  font-size: 26rpx;
+  color: #333;
+  display: block;
+}
+
+.member-role {
+  font-size: 20rpx;
+  color: #94A3B8;
+}
+
+.member-stats {
+  text-align: right;
+}
+
+.member-count {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #F97316;
+  display: block;
+}
+
+.member-label {
+  font-size: 20rpx;
+  color: #666;
+}
+
+.empty-tip {
+  text-align: center;
+  padding: 40rpx;
+  color: #94A3B8;
+  font-size: 26rpx;
+}
+
+/* 客户列表 */
+.customer-item {
+  display: flex;
+  align-items: center;
+  padding: 16rpx 0;
+  border-bottom: 1rpx solid #FED7AA;
+}
+
+.customer-item:last-child {
+  border-bottom: none;
+}
+
+.customer-avatar {
+  width: 64rpx;
+  height: 64rpx;
+  border-radius: 50%;
+  background-color: #F5FAFE;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 16rpx;
+}
+
+.customer-icon {
+  font-size: 32rpx;
+}
+
+.customer-info {
+  flex: 1;
+}
+
+.customer-name {
+  font-size: 26rpx;
+  color: #333;
+  display: block;
+}
+
+.customer-meta {
+  font-size: 20rpx;
+  color: #94A3B8;
+}
+
+.customer-stats {
+  text-align: right;
+}
+
+.customer-orders {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #F97316;
+  display: block;
+}
+
+.customer-label {
+  font-size: 20rpx;
+  color: #666;
+}
+
+/* 操作按钮 */
+.action-bar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background-color: #fff;
+  padding: 20rpx 30rpx;
+  display: flex;
+  gap: 20rpx;
+  box-shadow: 0 -2rpx 12rpx rgba(0,0,0,0.05);
+}
+
+.btn-manage, .btn-customers {
+  flex: 1;
+  height: 80rpx;
+  line-height: 80rpx;
+  border-radius: 40rpx;
+  font-size: 28rpx;
+  border: none;
+}
+
+.btn-manage {
+  background-color: #F97316;
+  color: #fff;
+}
+
+.btn-customers {
+  background-color: #fff;
+  color: #F97316;
+  border: 2rpx solid #F97316;
+}
+</style>

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-b20beb73bffb69142e6b211bdfd69a0117f6ace9
+78bc1b329a155e5954d75bf647dd7a7e43feaf67

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1510",
+  "version": "1.0.1511",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 18 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,24 @@
 
 ---
 
+## v1.0.1511 (2026-10-05)
+
+### 新功能
+- add batch shelve + category selection for product management
+
+### 其他
+-   * Backend: POST /api/admin/product/batch-shelve with {productIds, shelve}
+-   * ProductService.batchAdminShelve() validates all products are in approvable status before bulk update
+-   * Frontend: ProductManage.vue - multi-select table row + dropdown '批量上架/下架' with confirmation
+-   * api/admin.js: batchShelveProduct()
+- - Task 2: Category selection on product edit page
+-   * ProductEdit.vue adds categoryId field to form, loaded via getCategoryTree(true)
+-   * UI uses el-option-group grouped by parent category, searchable/filterable
+-   * On save/edit, categoryId is included in payload, enabling frontend mall category filtering
+- - Docs: PROJECT-OVERVIEW.md and API_REFERENCE.md updated with new endpoints
+- 
+
+
 ## v1.0.1510 (2026-10-05)
 
 ### Bug 修复

+ 19 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1510
+> 当前版本: v1.0.1511
 
 ## 历史版本
 
@@ -8,6 +8,24 @@
 
 ---
 
+## v1.0.1511 (2026-10-05)
+
+### 新功能
+- add batch shelve + category selection for product management
+
+### 其他
+-   * Backend: POST /api/admin/product/batch-shelve with {productIds, shelve}
+-   * ProductService.batchAdminShelve() validates all products are in approvable status before bulk update
+-   * Frontend: ProductManage.vue - multi-select table row + dropdown '批量上架/下架' with confirmation
+-   * api/admin.js: batchShelveProduct()
+- - Task 2: Category selection on product edit page
+-   * ProductEdit.vue adds categoryId field to form, loaded via getCategoryTree(true)
+-   * UI uses el-option-group grouped by parent category, searchable/filterable
+-   * On save/edit, categoryId is included in payload, enabling frontend mall category filtering
+- - Docs: PROJECT-OVERVIEW.md and API_REFERENCE.md updated with new endpoints
+- 
+
+
 ## v1.0.1510 (2026-10-05)
 
 ### Bug 修复