Browse Source

feat: 新增会员营期转移功能

- 新增会员营期转移页面 (src/views/user/periodTransfer/index.vue)
- 新增营期转移相关API接口
  - getPeriodTransferOptions: 获取下拉框数据(营期列表和销售列表)
  - loadPeriodUserList: 加载营期会员列表
  - periodTransfer: 提交会员营期转移
- 功能特性:
  - 支持源营期/源销售和目标营期/目标销售选择
  - 支持从列表批量选择会员或手动输入会员ID
  - 支持会员搜索(昵称、手机号)和分页
  - 支持填写转移原因
  - 提供详细的转移结果反馈(成功/失败统计)
xw 2 weeks ago
parent
commit
ba63c8f669
2 changed files with 640 additions and 0 deletions
  1. 26 0
      src/api/user/fsUser.js
  2. 614 0
      src/views/user/periodTransfer/index.vue

+ 26 - 0
src/api/user/fsUser.js

@@ -103,3 +103,29 @@ export function batchSendCourse(data) {
     data: data
   })
 }
+
+// 会员营期转移
+export function periodTransfer(data) {
+  return request({
+    url: '/user/fsUser/periodTransfer',
+    method: 'post',
+    data: data
+  })
+}
+
+// 获取会员营期转移下拉框数据
+export function getPeriodTransferOptions() {
+  return request({
+    url: '/user/fsUser/getPeriodTransferOptions',
+    method: 'get'
+  })
+}
+
+// 加载营期会员列表
+export function loadPeriodUserList(params) {
+  return request({
+    url: '/user/fsUser/loadPeriodUserList',
+    method: 'get',
+    params: params
+  })
+}

+ 614 - 0
src/views/user/periodTransfer/index.vue

@@ -0,0 +1,614 @@
+<template>
+  <div class="app-container">
+    <!-- 顶部提示 -->
+    <el-alert
+      title="会员营期转移说明"
+      type="info"
+      :closable="false"
+      style="margin-bottom: 20px;">
+      <div slot="default">
+        <p>1. 选择源营期和目标营期后,可将会员从源营期转移至目标营期</p>
+        <p>2. 需要同时选择源销售和目标销售</p>
+        <p>3. 可批量选择会员进行转移,也可通过手动输入会员ID</p>
+        <p>4. 转移操作会生成审批记录,请填写转移原因</p>
+      </div>
+    </el-alert>
+
+    <!-- 转移表单 -->
+    <el-card class="transfer-card" shadow="never">
+      <div slot="header">
+        <span style="font-weight: bold; font-size: 16px;">营期转移配置</span>
+      </div>
+
+      <el-form :model="transferForm" :rules="transferRules" ref="transferForm" label-width="120px">
+        <el-row :gutter="20">
+          <!-- 源营期信息 -->
+          <el-col :span="12">
+            <div class="section-title">源营期信息</div>
+            <el-form-item label="源营期" prop="sourcePeriodId">
+              <el-select
+                v-model="transferForm.sourcePeriodId"
+                filterable
+                placeholder="请选择源营期"
+                @change="handleSourcePeriodChange"
+                style="width: 100%">
+                <el-option
+                  v-for="item in sourcePeriodOptions"
+                  :key="item.periodId"
+                  :label="item.periodName"
+                  :value="item.periodId">
+                </el-option>
+              </el-select>
+            </el-form-item>
+
+            <el-form-item label="源销售" prop="sourceCompanyUserId">
+              <el-select
+                v-model="transferForm.sourceCompanyUserId"
+                filterable
+                placeholder="请选择源销售"
+                @change="handleSourceCompanyUserChange"
+                style="width: 100%">
+                <el-option
+                  v-for="item in sourceCompanyUserOptions"
+                  :key="item.userId"
+                  :label="item.nickName"
+                  :value="item.userId">
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+
+          <!-- 目标营期信息 -->
+          <el-col :span="12">
+            <div class="section-title">目标营期信息</div>
+            <el-form-item label="目标营期" prop="targetPeriodId">
+              <el-select
+                v-model="transferForm.targetPeriodId"
+                filterable
+                placeholder="请选择目标营期"
+                style="width: 100%">
+                <el-option
+                  v-for="item in targetPeriodOptions"
+                  :key="item.periodId"
+                  :label="item.periodName"
+                  :value="item.periodId">
+                </el-option>
+              </el-select>
+            </el-form-item>
+
+            <el-form-item label="目标销售" prop="targetCompanyUserId">
+              <el-select
+                v-model="transferForm.targetCompanyUserId"
+                filterable
+                placeholder="请选择目标销售"
+                style="width: 100%">
+                <el-option
+                  v-for="item in targetCompanyUserOptions"
+                  :key="item.userId"
+                  :label="item.nickName"
+                  :value="item.userId">
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 会员选择 -->
+        <div class="section-title" style="margin-top: 20px;">会员选择</div>
+        <el-form-item label="选择方式" prop="selectMode">
+          <el-radio-group v-model="selectMode" @change="handleSelectModeChange">
+            <el-radio label="list">从列表选择</el-radio>
+            <el-radio label="manual">手动输入ID</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <!-- 手动输入模式 -->
+        <el-form-item v-if="selectMode === 'manual'" label="会员ID" prop="manualUserIds">
+          <el-input
+            v-model="manualUserIds"
+            type="textarea"
+            :rows="3"
+            placeholder="请输入会员ID,多个ID用英文逗号或换行分隔,例如:1001,1002,1003"
+            @blur="handleManualUserIdsChange">
+          </el-input>
+          <div style="margin-top: 5px; color: #909399; font-size: 12px;">
+            已输入 {{ transferForm.userIds.length }} 个会员ID
+          </div>
+        </el-form-item>
+
+        <!-- 转移原因 -->
+        <el-form-item label="转移原因">
+          <el-input
+            v-model="transferForm.reason"
+            type="textarea"
+            :rows="3"
+            maxlength="200"
+            show-word-limit
+            placeholder="请输入转移原因(选填)">
+          </el-input>
+        </el-form-item>
+
+        <!-- 操作按钮 -->
+        <el-form-item>
+          <el-button type="primary" @click="handleLoadUsers" :loading="loadingUsers" v-if="selectMode === 'list'">
+            加载会员列表
+          </el-button>
+          <el-button type="primary" @click="handleSubmitTransfer" :loading="submitLoading" :disabled="transferForm.userIds.length === 0">
+            提交转移({{ transferForm.userIds.length }}人)
+          </el-button>
+          <el-button @click="handleResetForm">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <!-- 会员列表(列表选择模式) -->
+    <el-card v-if="selectMode === 'list' && showUserList" class="user-list-card" shadow="never" style="margin-top: 20px;">
+      <div slot="header" style="display: flex; justify-content: space-between; align-items: center;">
+        <span style="font-weight: bold; font-size: 16px;">
+          会员列表(已选:{{ transferForm.userIds.length }}人)
+        </span>
+        <el-button size="small" type="text" @click="handleClearSelection">清空选择</el-button>
+      </div>
+
+      <!-- 会员搜索 -->
+      <el-form :model="userQueryParams" :inline="true" size="small" style="margin-bottom: 15px;">
+        <el-form-item label="会员昵称">
+          <el-input
+            v-model="userQueryParams.nickName"
+            placeholder="请输入会员昵称"
+            clearable
+            style="width: 200px"
+            @keyup.enter.native="handleQueryUsers">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="手机号">
+          <el-input
+            v-model="userQueryParams.phone"
+            placeholder="请输入手机号"
+            clearable
+            style="width: 200px"
+            @keyup.enter.native="handleQueryUsers">
+          </el-input>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" @click="handleQueryUsers">搜索</el-button>
+          <el-button icon="el-icon-refresh" @click="handleResetUserQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+
+      <!-- 会员表格 -->
+      <el-table
+        ref="userTable"
+        v-loading="loadingUsers"
+        :data="userList"
+        @selection-change="handleUserSelectionChange"
+        border
+        max-height="500">
+        <el-table-column type="selection" width="55" align="center" :reserve-selection="true" />
+        <el-table-column label="会员ID" align="center" prop="userId" width="100" />
+        <el-table-column label="会员昵称" align="center" prop="nickName" min-width="120">
+          <template slot-scope="scope">
+            <div style="display: flex; align-items: center; justify-content: center;">
+              <el-avatar :size="30" :src="scope.row.avatar" style="margin-right: 8px;"></el-avatar>
+              <span>{{ scope.row.nickName }}</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="手机号" align="center" prop="phone" width="120" />
+        <el-table-column label="注册时间" align="center" prop="createTime" width="160" />
+      </el-table>
+
+      <!-- 分页 -->
+      <pagination
+        v-show="userTotal > 0"
+        :total="userTotal"
+        :page.sync="userQueryParams.pageNum"
+        :limit.sync="userQueryParams.pageSize"
+        @pagination="handleLoadUsers"
+      />
+    </el-card>
+
+    <!-- 转移结果对话框 -->
+    <el-dialog
+      title="转移结果"
+      :visible.sync="resultDialogVisible"
+      width="600px"
+      center>
+      <div style="text-align: center; padding: 20px 0;">
+        <i :class="resultIcon" :style="{ fontSize: '60px', color: resultColor }"></i>
+        <h2 style="margin: 20px 0 10px;">{{ resultTitle }}</h2>
+        <p style="font-size: 16px; color: #606266;">{{ resultMessage }}</p>
+        <div v-if="resultData" style="margin-top: 20px; padding: 15px; background-color: #f5f7fa; border-radius: 4px;">
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <div style="font-size: 14px; color: #909399;">成功数量</div>
+              <div style="font-size: 24px; font-weight: bold; color: #67C23A; margin-top: 5px;">
+                {{ resultData.successCount }}
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div style="font-size: 14px; color: #909399;">失败数量</div>
+              <div style="font-size: 24px; font-weight: bold; color: #F56C6C; margin-top: 5px;">
+                {{ resultData.failCount }}
+              </div>
+            </el-col>
+          </el-row>
+          <div v-if="resultData.approvalId" style="margin-top: 15px; font-size: 13px; color: #909399;">
+            审批记录ID: {{ resultData.approvalId }}
+          </div>
+        </div>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="handleCloseResult">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { periodTransfer, getPeriodTransferOptions, loadPeriodUserList } from "@/api/user/fsUser";
+
+export default {
+  name: "PeriodTransfer",
+  data() {
+    return {
+      // 选择模式
+      selectMode: 'list',
+      // 手动输入的会员ID
+      manualUserIds: '',
+      // 是否显示会员列表
+      showUserList: false,
+
+      // 转移表单
+      transferForm: {
+        sourcePeriodId: null,
+        sourceCompanyUserId: null,
+        targetPeriodId: null,
+        targetCompanyUserId: null,
+        userIds: [],
+        reason: ''
+      },
+
+      // 表单验证规则
+      transferRules: {
+        sourcePeriodId: [
+          { required: true, message: '请选择源营期', trigger: 'change' }
+        ],
+        sourceCompanyUserId: [
+          { required: true, message: '请选择源销售', trigger: 'change' }
+        ],
+        targetPeriodId: [
+          { required: true, message: '请选择目标营期', trigger: 'change' }
+        ],
+        targetCompanyUserId: [
+          { required: true, message: '请选择目标销售', trigger: 'change' }
+        ]
+      },
+
+      // 源营期选项
+      sourcePeriodOptions: [],
+      // 目标营期选项
+      targetPeriodOptions: [],
+
+      // 源销售选项
+      sourceCompanyUserOptions: [],
+      // 目标销售选项
+      targetCompanyUserOptions: [],
+
+      // 会员列表
+      userList: [],
+      userTotal: 0,
+      loadingUsers: false,
+      // 会员查询参数
+      userQueryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        periodId: null,
+        companyUserId: null,
+        nickName: '',
+        phone: ''
+      },
+
+      // 提交加载状态
+      submitLoading: false,
+
+      // 结果对话框
+      resultDialogVisible: false,
+      resultTitle: '',
+      resultMessage: '',
+      resultData: null,
+      resultIcon: '',
+      resultColor: ''
+    };
+  },
+
+  created() {
+    // 初始化加载营期和销售选项
+    this.loadTransferOptions();
+  },
+
+  methods: {
+    /**
+     * 加载营期转移下拉框数据
+     */
+    loadTransferOptions() {
+      getPeriodTransferOptions().then(response => {
+        if (response.code === 200) {
+          // 设置营期列表(源和目标使用同一份数据)
+          this.sourcePeriodOptions = response.periodList || [];
+          this.targetPeriodOptions = response.periodList || [];
+          
+          // 设置销售列表(源和目标使用同一份数据)
+          this.sourceCompanyUserOptions = response.companyUserList || [];
+          this.targetCompanyUserOptions = response.companyUserList || [];
+          
+          console.log('营期列表:', this.sourcePeriodOptions);
+          console.log('销售列表:', this.sourceCompanyUserOptions);
+        }
+      }).catch(error => {
+        console.error('加载下拉框数据失败:', error);
+        this.$message.error('加载下拉框数据失败');
+      });
+    },
+
+    /**
+     * 源营期改变
+     */
+    handleSourcePeriodChange(value) {
+      this.userQueryParams.periodId = value;
+      this.showUserList = false;
+      this.userList = [];
+      this.transferForm.userIds = [];
+    },
+
+    /**
+     * 源销售改变
+     */
+    handleSourceCompanyUserChange(value) {
+      this.userQueryParams.companyUserId = value;
+      this.showUserList = false;
+      this.userList = [];
+      this.transferForm.userIds = [];
+    },
+
+    /**
+     * 选择模式改变
+     */
+    handleSelectModeChange(value) {
+      this.transferForm.userIds = [];
+      this.manualUserIds = '';
+      this.showUserList = false;
+      if (this.$refs.userTable) {
+        this.$refs.userTable.clearSelection();
+      }
+    },
+
+    /**
+     * 手动输入会员ID变化
+     */
+    handleManualUserIdsChange() {
+      if (!this.manualUserIds.trim()) {
+        this.transferForm.userIds = [];
+        return;
+      }
+
+      // 分割ID(支持逗号和换行)
+      const ids = this.manualUserIds
+        .split(/[,\n\r]/)
+        .map(id => id.trim())
+        .filter(id => id && /^\d+$/.test(id))
+        .map(id => parseInt(id));
+
+      // 去重
+      this.transferForm.userIds = [...new Set(ids)];
+    },
+
+    /**
+     * 加载会员列表
+     */
+    handleLoadUsers() {
+      // 验证必填项
+      if (!this.transferForm.sourcePeriodId) {
+        this.$message.warning('请先选择源营期');
+        return;
+      }
+      if (!this.transferForm.sourceCompanyUserId) {
+        this.$message.warning('请先选择源销售');
+        return;
+      }
+
+      this.loadingUsers = true;
+      
+      // 使用新接口加载会员列表
+      loadPeriodUserList({
+        sourcePeriodId: this.transferForm.sourcePeriodId,
+        sourceCompanyUserId: this.transferForm.sourceCompanyUserId,
+        pageNum: this.userQueryParams.pageNum,
+        pageSize: this.userQueryParams.pageSize,
+        nickName: this.userQueryParams.nickName,
+        phone: this.userQueryParams.phone
+      }).then(response => {
+        if (response.code === 200) {
+          this.userList = response.userList || [];
+          this.userTotal = response.total || 0;
+          this.showUserList = true;
+          console.log('加载会员列表成功:', this.userList.length, '条');
+        }
+      }).catch(error => {
+        console.error('加载会员列表失败:', error);
+        this.$message.error('加载会员列表失败');
+      }).finally(() => {
+        this.loadingUsers = false;
+      });
+    },
+
+    /**
+     * 查询会员
+     */
+    handleQueryUsers() {
+      this.userQueryParams.pageNum = 1;
+      this.handleLoadUsers();
+    },
+
+    /**
+     * 重置会员查询
+     */
+    handleResetUserQuery() {
+      this.userQueryParams.nickName = '';
+      this.userQueryParams.phone = '';
+      this.handleQueryUsers();
+    },
+
+    /**
+     * 会员选择改变
+     */
+    handleUserSelectionChange(selection) {
+      this.transferForm.userIds = selection.map(item => item.userId);
+    },
+
+    /**
+     * 清空选择
+     */
+    handleClearSelection() {
+      if (this.$refs.userTable) {
+        this.$refs.userTable.clearSelection();
+      }
+      this.transferForm.userIds = [];
+    },
+
+    /**
+     * 提交转移
+     */
+    handleSubmitTransfer() {
+      this.$refs.transferForm.validate(valid => {
+        if (!valid) {
+          return;
+        }
+
+        if (this.transferForm.userIds.length === 0) {
+          this.$message.warning('请至少选择一个会员');
+          return;
+        }
+
+        // 确认提示
+        this.$confirm(
+          `确定要将 ${this.transferForm.userIds.length} 个会员从源营期转移到目标营期吗?`,
+          '转移确认',
+          {
+            confirmButtonText: '确定转移',
+            cancelButtonText: '取消',
+            type: 'warning'
+          }
+        ).then(() => {
+          this.submitTransfer();
+        });
+      });
+    },
+
+    /**
+     * 执行转移
+     */
+    submitTransfer() {
+      this.submitLoading = true;
+
+      periodTransfer(this.transferForm).then(response => {
+        if (response.code === 200) {
+          this.resultTitle = '转移成功';
+          this.resultMessage = response.msg || '会员营期转移已完成';
+          this.resultData = response.data || null;
+          this.resultIcon = 'el-icon-success';
+          this.resultColor = '#67C23A';
+          this.resultDialogVisible = true;
+
+          // 清空表单
+          this.handleResetForm();
+        } else {
+          this.$message.error(response.msg || '转移失败');
+        }
+      }).catch(error => {
+        this.resultTitle = '转移失败';
+        this.resultMessage = error.msg || '转移过程中发生错误,请稍后重试';
+        this.resultData = null;
+        this.resultIcon = 'el-icon-error';
+        this.resultColor = '#F56C6C';
+        this.resultDialogVisible = true;
+      }).finally(() => {
+        this.submitLoading = false;
+      });
+    },
+
+    /**
+     * 关闭结果对话框
+     */
+    handleCloseResult() {
+      this.resultDialogVisible = false;
+      this.resultData = null;
+    },
+
+    /**
+     * 重置表单
+     */
+    handleResetForm() {
+      this.transferForm = {
+        sourcePeriodId: null,
+        sourceCompanyUserId: null,
+        targetPeriodId: null,
+        targetCompanyUserId: null,
+        userIds: [],
+        reason: ''
+      };
+      this.manualUserIds = '';
+      this.showUserList = false;
+      this.userList = [];
+      this.userTotal = 0;
+      this.userQueryParams = {
+        pageNum: 1,
+        pageSize: 10,
+        periodId: null,
+        companyUserId: null,
+        nickName: '',
+        phone: ''
+      };
+      if (this.$refs.userTable) {
+        this.$refs.userTable.clearSelection();
+      }
+      if (this.$refs.transferForm) {
+        this.$refs.transferForm.clearValidate();
+      }
+    }
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.transfer-card {
+  ::v-deep .el-card__header {
+    background-color: #f5f7fa;
+    padding: 15px 20px;
+  }
+}
+
+.user-list-card {
+  ::v-deep .el-card__header {
+    background-color: #f5f7fa;
+    padding: 15px 20px;
+  }
+}
+
+.section-title {
+  font-size: 14px;
+  font-weight: bold;
+  color: #303133;
+  margin-bottom: 15px;
+  padding-bottom: 8px;
+  border-bottom: 2px solid #409EFF;
+}
+
+::v-deep .el-alert {
+  p {
+    margin: 5px 0;
+    font-size: 13px;
+    line-height: 1.8;
+  }
+}
+</style>