|
@@ -0,0 +1,689 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="app-container">
|
|
|
|
|
+ <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="80px">
|
|
|
|
|
+ <el-form-item label="会员ID" prop="userId">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="queryParams.userId"
|
|
|
|
|
+ placeholder="请输入会员ID"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ @keyup.enter.native="handleQuery"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="用户昵称" prop="nickName">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="queryParams.nickName"
|
|
|
|
|
+ placeholder="请输入用户昵称"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ @keyup.enter.native="handleQuery"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="手机号码" prop="phone">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="queryParams.phone"
|
|
|
|
|
+ placeholder="请输入手机号码"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ @keyup.enter.native="handleQuery"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="微信绑定" prop="bindStatus">
|
|
|
|
|
+ <el-select v-model="queryParams.bindStatus" placeholder="请选择绑定状态" clearable size="small">
|
|
|
|
|
+ <el-option label="未绑定" :value="0" />
|
|
|
|
|
+ <el-option label="已绑定" :value="1" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
|
|
|
|
+ <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+
|
|
|
|
|
+ <el-row :gutter="10" class="mb8">
|
|
|
|
|
+ <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table border v-loading="loading" :data="userList">
|
|
|
|
|
+ <el-table-column label="会员ID" align="center" prop="userId" width="120" />
|
|
|
|
|
+ <el-table-column label="用户昵称" align="center" prop="nickName" min-width="150" />
|
|
|
|
|
+ <el-table-column label="会员头像" align="center" width="80">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <el-popover v-if="scope.row.avatar" placement="right" trigger="hover">
|
|
|
|
|
+ <img
|
|
|
|
|
+ slot="reference"
|
|
|
|
|
+ :src="scope.row.avatar"
|
|
|
|
|
+ style="width: 40px; height: 40px; border-radius: 50%; object-fit: cover"
|
|
|
|
|
+ />
|
|
|
|
|
+ <img :src="scope.row.avatar" style="max-width: 200px; max-height: 200px; border-radius: 4px" />
|
|
|
|
|
+ </el-popover>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="手机号码" align="center" min-width="140">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <span>{{ phoneMap[scope.row.phone] || scope.row.phone }}</span>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="scope.row.phone && !phoneMap[scope.row.phone]"
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ icon="el-icon-search"
|
|
|
|
|
+ size="mini"
|
|
|
|
|
+ title="查看手机号"
|
|
|
|
|
+ @click="handleDecryptPhone(scope.row.phone)"
|
|
|
|
|
+ />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="微信绑定" align="center" width="100">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <el-tag v-if="scope.row.bindUserId != null || scope.row.externalContactId != null" type="success" size="mini">已绑定</el-tag>
|
|
|
|
|
+ <el-tag v-else type="info" size="mini">未绑定</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="分配时间" align="center" width="180">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <span>{{ formatDate(scope.row.createTime) }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" align="center" width="180" fixed="right">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="scope.row.bindUserId == null && scope.row.externalContactId == null"
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ size="mini"
|
|
|
|
|
+ @click="handleOpenBind(scope.row)"
|
|
|
|
|
+ >绑定用户微信</el-button>
|
|
|
|
|
+ <template v-else>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ size="mini"
|
|
|
|
|
+ @click="handleShowDetail(scope.row)"
|
|
|
|
|
+ >详情</el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ size="mini"
|
|
|
|
|
+ style="color: #F56C6C;"
|
|
|
|
|
+ @click="handleUnbind(scope.row)"
|
|
|
|
|
+ >解绑</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <pagination
|
|
|
|
|
+ v-show="total > 0"
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ :page.sync="queryParams.pageNum"
|
|
|
|
|
+ :limit.sync="queryParams.pageSize"
|
|
|
|
|
+ @pagination="getList"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 绑定微信用户弹窗 -->
|
|
|
|
|
+ <el-dialog title="选择要绑定的微信用户" :visible.sync="bindDialogVisible" width="780px" append-to-body :close-on-click-modal="false">
|
|
|
|
|
+ <div class="bind-mode-switch">
|
|
|
|
|
+ <span class="bind-mode-label">绑定方式:</span>
|
|
|
|
|
+ <el-radio-group v-model="bindMode" size="small">
|
|
|
|
|
+ <el-radio-button label="manual">
|
|
|
|
|
+ <i class="el-icon-user"></i> 手动发课
|
|
|
|
|
+ </el-radio-button>
|
|
|
|
|
+ <el-radio-button label="auto">
|
|
|
|
|
+ <i class="el-icon-connection"></i> 自动发课
|
|
|
|
|
+ </el-radio-button>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 手动发课:会员列表 + 看课项目 -->
|
|
|
|
|
+ <template v-if="bindMode === 'manual'">
|
|
|
|
|
+ <div class="bind-section" style="margin-top: 16px;">
|
|
|
|
|
+ <div class="bind-section-title">
|
|
|
|
|
+ <i class="el-icon-search"></i> 会员列表
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-form :model="bindSearchParams" :inline="true" size="small">
|
|
|
|
|
+ <el-form-item label="会员ID">
|
|
|
|
|
+ <el-input v-model="bindSearchParams.userId" placeholder="请输入会员ID" clearable @keyup.enter.native="searchBindUserList" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="用户昵称">
|
|
|
|
|
+ <el-input v-model="bindSearchParams.nickName" placeholder="请输入用户昵称" clearable @keyup.enter.native="searchBindUserList" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" icon="el-icon-search" size="mini" @click="searchBindUserList">搜索</el-button>
|
|
|
|
|
+ <el-button icon="el-icon-refresh" size="mini" @click="resetBindSearch">重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <el-table ref="manualMemberTable" border v-loading="bindListLoading" :data="bindUserList" @selection-change="handleManualMemberSelect" max-height="260">
|
|
|
|
|
+ <el-table-column type="selection" width="45" align="center" />
|
|
|
|
|
+ <el-table-column label="会员ID" align="center" prop="userId" width="120" />
|
|
|
|
|
+ <el-table-column label="用户昵称" align="center" prop="nickName" min-width="160" />
|
|
|
|
|
+ <el-table-column label="头像" align="center" width="70">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <el-popover v-if="scope.row.avatar" placement="right" trigger="hover">
|
|
|
|
|
+ <img
|
|
|
|
|
+ slot="reference"
|
|
|
|
|
+ :src="scope.row.avatar"
|
|
|
|
|
+ style="width: 36px; height: 36px; border-radius: 50%; object-fit: cover"
|
|
|
|
|
+ />
|
|
|
|
|
+ <img :src="scope.row.avatar" style="max-width: 200px; max-height: 200px; border-radius: 4px" />
|
|
|
|
|
+ </el-popover>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <pagination
|
|
|
|
|
+ v-show="bindTotal > 0"
|
|
|
|
|
+ :total="bindTotal"
|
|
|
|
|
+ :page.sync="bindSearchParams.pageNum"
|
|
|
|
|
+ :limit.sync="bindSearchParams.pageSize"
|
|
|
|
|
+ @pagination="searchBindUserList"
|
|
|
|
|
+ layout="total, prev, pager, next"
|
|
|
|
|
+ small
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-divider />
|
|
|
|
|
+ <el-form ref="projectForm" size="small" label-width="80px">
|
|
|
|
|
+ <el-form-item label="看课项目" prop="projectId" :rules="[{ required: true, message: '请选择看课项目', trigger: 'change' }]">
|
|
|
|
|
+ <el-select v-model="projectId" placeholder="请选择看课项目" clearable style="width: 100%;">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="item in projectOptions"
|
|
|
|
|
+ :key="item.dictValue"
|
|
|
|
|
+ :label="item.dictLabel"
|
|
|
|
|
+ :value="Number(item.dictValue)"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 自动发课:会员列表 + 外部联系人 -->
|
|
|
|
|
+ <template v-else>
|
|
|
|
|
+ <div class="bind-section" style="margin-top: 16px;">
|
|
|
|
|
+ <div class="bind-section-title">
|
|
|
|
|
+ <i class="el-icon-user"></i> 会员列表
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-form :model="bindSearchParams" :inline="true" size="small">
|
|
|
|
|
+ <el-form-item label="会员ID">
|
|
|
|
|
+ <el-input v-model="bindSearchParams.userId" placeholder="请输入会员ID" clearable @keyup.enter.native="searchBindUserList" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="用户昵称">
|
|
|
|
|
+ <el-input v-model="bindSearchParams.nickName" placeholder="请输入用户昵称" clearable @keyup.enter.native="searchBindUserList" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" icon="el-icon-search" size="mini" @click="searchBindUserList">搜索</el-button>
|
|
|
|
|
+ <el-button icon="el-icon-refresh" size="mini" @click="resetBindSearch">重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <el-table ref="autoMemberTable" border v-loading="bindListLoading" :data="bindUserList" @selection-change="handleAutoMemberSelect" max-height="200">
|
|
|
|
|
+ <el-table-column type="selection" width="45" align="center" />
|
|
|
|
|
+ <el-table-column label="会员ID" align="center" prop="userId" width="120" />
|
|
|
|
|
+ <el-table-column label="用户昵称" align="center" prop="nickName" min-width="160" />
|
|
|
|
|
+ <el-table-column label="头像" align="center" width="70">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <el-popover v-if="scope.row.avatar" placement="right" trigger="hover">
|
|
|
|
|
+ <img
|
|
|
|
|
+ slot="reference"
|
|
|
|
|
+ :src="scope.row.avatar"
|
|
|
|
|
+ style="width: 36px; height: 36px; border-radius: 50%; object-fit: cover"
|
|
|
|
|
+ />
|
|
|
|
|
+ <img :src="scope.row.avatar" style="max-width: 200px; max-height: 200px; border-radius: 4px" />
|
|
|
|
|
+ </el-popover>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <pagination
|
|
|
|
|
+ v-show="bindTotal > 0"
|
|
|
|
|
+ :total="bindTotal"
|
|
|
|
|
+ :page.sync="bindSearchParams.pageNum"
|
|
|
|
|
+ :limit.sync="bindSearchParams.pageSize"
|
|
|
|
|
+ @pagination="searchBindUserList"
|
|
|
|
|
+ layout="total, prev, pager, next"
|
|
|
|
|
+ small
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="bind-section" style="margin-top: 16px;">
|
|
|
|
|
+ <div class="bind-section-title">
|
|
|
|
|
+ <i class="el-icon-connection"></i> 外部联系人
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-form :model="externalContactSearchParams" :inline="true" size="small">
|
|
|
|
|
+ <el-form-item label="用户昵称">
|
|
|
|
|
+ <el-input v-model="externalContactSearchParams.name" placeholder="请输入用户昵称" clearable @keyup.enter.native="searchExternalContactList" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="企业微信">
|
|
|
|
|
+ <el-input v-model="externalContactSearchParams.corpName" placeholder="请输入企业微信" clearable @keyup.enter.native="searchExternalContactList" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" icon="el-icon-search" size="mini" @click="searchExternalContactList">搜索</el-button>
|
|
|
|
|
+ <el-button icon="el-icon-refresh" size="mini" @click="resetExternalSearch">重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <el-table ref="externalContactTable" border v-loading="externalListLoading" :data="externalContactList" @selection-change="handleExternalSelect" max-height="200">
|
|
|
|
|
+ <el-table-column type="selection" width="45" align="center" />
|
|
|
|
|
+ <el-table-column label="外部联系人ID" align="center" prop="id" width="110" />
|
|
|
|
|
+ <el-table-column label="头像" align="center" width="60">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <el-popover v-if="scope.row.avatar" placement="right" trigger="hover">
|
|
|
|
|
+ <img
|
|
|
|
|
+ slot="reference"
|
|
|
|
|
+ :src="scope.row.avatar"
|
|
|
|
|
+ style="width: 32px; height: 32px; border-radius: 50%; object-fit: cover"
|
|
|
|
|
+ />
|
|
|
|
|
+ <img :src="scope.row.avatar" style="max-width: 200px; max-height: 200px; border-radius: 4px" />
|
|
|
|
|
+ </el-popover>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="用户昵称" align="center" prop="name" min-width="120" />
|
|
|
|
|
+ <el-table-column label="企业微信" align="center" prop="corpName" min-width="150" />
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <pagination
|
|
|
|
|
+ v-show="externalTotal > 0"
|
|
|
|
|
+ :total="externalTotal"
|
|
|
|
|
+ :page.sync="externalContactSearchParams.pageNum"
|
|
|
|
|
+ :limit.sync="externalContactSearchParams.pageSize"
|
|
|
|
|
+ @pagination="searchExternalContactList"
|
|
|
|
|
+ layout="total, prev, pager, next"
|
|
|
|
|
+ small
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <div slot="footer" class="dialog-footer">
|
|
|
|
|
+ <el-button @click="bindDialogVisible = false">取 消</el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="bindConfirmLoading" :disabled="!canConfirmBind" @click="confirmBind">确 定</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 绑定用户详情弹窗 -->
|
|
|
|
|
+ <el-dialog title="绑定用户详情" :visible.sync="detailDialogVisible" width="450px" append-to-body>
|
|
|
|
|
+ <div v-if="boundUserDetail || currentDetailRow" v-loading="detailLoading">
|
|
|
|
|
+ <!-- 自动发课:会员信息 + 外部联系人信息 -->
|
|
|
|
|
+ <template v-if="currentDetailRow && currentDetailRow.type === 2">
|
|
|
|
|
+ <!-- 会员信息 -->
|
|
|
|
|
+ <div class="bind-section" style="margin-bottom: 16px;">
|
|
|
|
|
+ <div class="bind-section-title">
|
|
|
|
|
+ <i class="el-icon-user"></i> 绑定会员
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div style="text-align: center; padding: 16px 0;">
|
|
|
|
|
+ <el-popover v-if="boundUserDetail && boundUserDetail.avatar" placement="right" trigger="hover">
|
|
|
|
|
+ <img
|
|
|
|
|
+ slot="reference"
|
|
|
|
|
+ :src="boundUserDetail.avatar"
|
|
|
|
|
+ style="width: 56px; height: 56px; border-radius: 50%; object-fit: cover"
|
|
|
|
|
+ />
|
|
|
|
|
+ <img :src="boundUserDetail.avatar" style="max-width: 200px; max-height: 200px; border-radius: 4px" />
|
|
|
|
|
+ </el-popover>
|
|
|
|
|
+ <div v-else style="width: 56px; height: 56px; border-radius: 50%; background: #eee; display: inline-block; line-height: 56px; color: #999;">无</div>
|
|
|
|
|
+ <p style="margin-top: 8px; font-size: 14px;">
|
|
|
|
|
+ <span style="color: #909399;">会员ID:</span>
|
|
|
|
|
+ <span style="color: #303133; font-weight: 500;">{{ boundUserDetail && boundUserDetail.userId }}</span>
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <p style="font-size: 14px;">
|
|
|
|
|
+ <span style="color: #909399;">昵称:</span>
|
|
|
|
|
+ <span style="color: #303133;">{{ boundUserDetail ? (boundUserDetail.nickName || '-') : '-' }}</span>
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 外部联系人信息 -->
|
|
|
|
|
+ <div class="bind-section">
|
|
|
|
|
+ <div class="bind-section-title">
|
|
|
|
|
+ <i class="el-icon-connection"></i> 外部联系人
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div style="text-align: center; padding: 16px 0;">
|
|
|
|
|
+ <el-popover v-if="currentDetailRow.externalContactAvatar" placement="right" trigger="hover">
|
|
|
|
|
+ <img
|
|
|
|
|
+ slot="reference"
|
|
|
|
|
+ :src="currentDetailRow.externalContactAvatar"
|
|
|
|
|
+ style="width: 56px; height: 56px; border-radius: 50%; object-fit: cover"
|
|
|
|
|
+ />
|
|
|
|
|
+ <img :src="currentDetailRow.externalContactAvatar" style="max-width: 200px; max-height: 200px; border-radius: 4px" />
|
|
|
|
|
+ </el-popover>
|
|
|
|
|
+ <div v-else style="width: 56px; height: 56px; border-radius: 50%; background: #eee; display: inline-block; line-height: 56px; color: #999;">无</div>
|
|
|
|
|
+ <p style="margin-top: 8px; font-size: 14px;">
|
|
|
|
|
+ <span style="color: #909399;">外部联系人ID:</span>
|
|
|
|
|
+ <span style="color: #303133;">{{ currentDetailRow.externalContactId }}</span>
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <p style="font-size: 14px;">
|
|
|
|
|
+ <span style="color: #909399;">昵称:</span>
|
|
|
|
|
+ <span style="color: #303133;">{{ currentDetailRow.externalContactName || '-' }}</span>
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <p style="text-align: center; margin-top: 12px; font-size: 14px;">
|
|
|
|
|
+ <span style="color: #909399;">类型:</span>
|
|
|
|
|
+ <el-tag type="warning" size="mini">自动发课</el-tag>
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 手动发课:会员信息 + 看课项目 -->
|
|
|
|
|
+ <template v-else>
|
|
|
|
|
+ <div style="text-align: center;">
|
|
|
|
|
+ <el-popover v-if="boundUserDetail && boundUserDetail.avatar" placement="right" trigger="hover">
|
|
|
|
|
+ <img
|
|
|
|
|
+ slot="reference"
|
|
|
|
|
+ :src="boundUserDetail.avatar"
|
|
|
|
|
+ style="width: 64px; height: 64px; border-radius: 50%; object-fit: cover"
|
|
|
|
|
+ />
|
|
|
|
|
+ <img :src="boundUserDetail.avatar" style="max-width: 200px; max-height: 200px; border-radius: 4px" />
|
|
|
|
|
+ </el-popover>
|
|
|
|
|
+ <div v-else style="width: 64px; height: 64px; border-radius: 50%; background: #eee; display: inline-block; line-height: 64px; color: #999;">无</div>
|
|
|
|
|
+ <p style="margin-top: 12px; font-size: 14px;">
|
|
|
|
|
+ <span style="color: #909399;">会员ID:</span>
|
|
|
|
|
+ <span style="color: #303133; font-weight: 500;">{{ boundUserDetail && boundUserDetail.userId }}</span>
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <p style="font-size: 14px;">
|
|
|
|
|
+ <span style="color: #909399;">昵称:</span>
|
|
|
|
|
+ <span style="color: #303133;">{{ boundUserDetail ? (boundUserDetail.nickName || '-') : '-' }}</span>
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <p v-if="currentDetailRow && currentDetailRow.projectId" style="font-size: 14px;">
|
|
|
|
|
+ <span style="color: #909399;">看课项目:</span>
|
|
|
|
|
+ <span style="color: #303133;">{{ projectMap[currentDetailRow.projectId] || currentDetailRow.projectId }}</span>
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <p style="font-size: 14px;">
|
|
|
|
|
+ <span style="color: #909399;">类型:</span>
|
|
|
|
|
+ <el-tag type="primary" size="mini">手动发课</el-tag>
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-else v-loading="detailLoading" style="min-height: 80px;" />
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import { listUserBindWx, decryptPhone, searchBindUser, bindWxUser, unbindWxUser, getBoundUserInfo, searchExternalContact } from '@/api/company/userBindWx'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'CompanyUserBindWx',
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ loading: false,
|
|
|
|
|
+ showSearch: true,
|
|
|
|
|
+ total: 0,
|
|
|
|
|
+ userList: [],
|
|
|
|
|
+ queryParams: {
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ userId: undefined,
|
|
|
|
|
+ nickName: undefined,
|
|
|
|
|
+ phone: undefined,
|
|
|
|
|
+ bindStatus: undefined
|
|
|
|
|
+ },
|
|
|
|
|
+ phoneMap: {},
|
|
|
|
|
+ // 绑定弹窗
|
|
|
|
|
+ bindDialogVisible: false,
|
|
|
|
|
+ bindListLoading: false,
|
|
|
|
|
+ bindConfirmLoading: false,
|
|
|
|
|
+ currentBindRow: null,
|
|
|
|
|
+ selectedBindUser: null,
|
|
|
|
|
+ bindUserList: [],
|
|
|
|
|
+ bindTotal: 0,
|
|
|
|
|
+ bindSearchParams: {
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ userId: undefined,
|
|
|
|
|
+ nickName: undefined
|
|
|
|
|
+ },
|
|
|
|
|
+ // 绑定模式:manual=手动 auto=自动
|
|
|
|
|
+ bindMode: 'manual',
|
|
|
|
|
+ // 外部联系人
|
|
|
|
|
+ externalContactSearchParams: {
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ name: undefined,
|
|
|
|
|
+ corpName: undefined
|
|
|
|
|
+ },
|
|
|
|
|
+ externalContactList: [],
|
|
|
|
|
+ externalTotal: 0,
|
|
|
|
|
+ externalListLoading: false,
|
|
|
|
|
+ selectedExternalContact: null,
|
|
|
|
|
+ selectedAutoMember: null,
|
|
|
|
|
+ // 看课项目
|
|
|
|
|
+ projectId: null,
|
|
|
|
|
+ projectOptions: [],
|
|
|
|
|
+ projectMap: {},
|
|
|
|
|
+ // 详情弹窗
|
|
|
|
|
+ currentDetailRow: null,
|
|
|
|
|
+ detailDialogVisible: false,
|
|
|
|
|
+ detailLoading: false,
|
|
|
|
|
+ boundUserDetail: null
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ canConfirmBind() {
|
|
|
|
|
+ if (this.bindMode === 'manual') {
|
|
|
|
|
+ return this.selectedBindUser != null && this.projectId != null
|
|
|
|
|
+ }
|
|
|
|
|
+ return this.selectedAutoMember != null && this.selectedExternalContact != null
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ watch: {
|
|
|
|
|
+ bindMode(val) {
|
|
|
|
|
+ // 切换模式时清除选中
|
|
|
|
|
+ this.selectedBindUser = null
|
|
|
|
|
+ this.selectedAutoMember = null
|
|
|
|
|
+ this.selectedExternalContact = null
|
|
|
|
|
+ this.projectId = null
|
|
|
|
|
+ if (val === 'auto') {
|
|
|
|
|
+ this.searchExternalContactList()
|
|
|
|
|
+ this.searchBindUserList()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ created() {
|
|
|
|
|
+ this.getList()
|
|
|
|
|
+ this.getDicts('sys_course_project').then(response => {
|
|
|
|
|
+ this.projectOptions = response.data || []
|
|
|
|
|
+ // 构建 projectId -> label 映射
|
|
|
|
|
+ const map = {}
|
|
|
|
|
+ ;(response.data || []).forEach(item => {
|
|
|
|
|
+ map[Number(item.dictValue)] = item.dictLabel
|
|
|
|
|
+ })
|
|
|
|
|
+ this.projectMap = map
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ getList() {
|
|
|
|
|
+ this.loading = true
|
|
|
|
|
+ listUserBindWx(this.queryParams).then(response => {
|
|
|
|
|
+ this.userList = response.rows
|
|
|
|
|
+ this.total = response.total
|
|
|
|
|
+ this.loading = false
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ this.loading = false
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ handleQuery() {
|
|
|
|
|
+ this.queryParams.pageNum = 1
|
|
|
|
|
+ this.getList()
|
|
|
|
|
+ },
|
|
|
|
|
+ resetQuery() {
|
|
|
|
|
+ this.resetForm('queryForm')
|
|
|
|
|
+ this.handleQuery()
|
|
|
|
|
+ },
|
|
|
|
|
+ handleDecryptPhone(encryptedPhone) {
|
|
|
|
|
+ decryptPhone(encryptedPhone).then(response => {
|
|
|
|
|
+ this.$set(this.phoneMap, encryptedPhone, response.data)
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ formatDate(timestamp) {
|
|
|
|
|
+ if (!timestamp) return ''
|
|
|
|
|
+ const date = new Date(timestamp)
|
|
|
|
|
+ const year = date.getFullYear()
|
|
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
|
|
+ const day = String(date.getDate()).padStart(2, '0')
|
|
|
|
|
+ const hours = String(date.getHours()).padStart(2, '0')
|
|
|
|
|
+ const minutes = String(date.getMinutes()).padStart(2, '0')
|
|
|
|
|
+ const seconds = String(date.getSeconds()).padStart(2, '0')
|
|
|
|
|
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
|
|
|
+ },
|
|
|
|
|
+ // 绑定微信用户相关
|
|
|
|
|
+ handleOpenBind(row) {
|
|
|
|
|
+ this.currentBindRow = row
|
|
|
|
|
+ this.bindMode = 'manual'
|
|
|
|
|
+ this.selectedBindUser = null
|
|
|
|
|
+ this.selectedAutoMember = null
|
|
|
|
|
+ this.selectedExternalContact = null
|
|
|
|
|
+ this.projectId = null
|
|
|
|
|
+ this.bindSearchParams = { pageNum: 1, pageSize: 10, userId: undefined, nickName: undefined }
|
|
|
|
|
+ this.externalContactSearchParams = { pageNum: 1, pageSize: 10, name: undefined, corpName: undefined }
|
|
|
|
|
+ this.bindDialogVisible = true
|
|
|
|
|
+ this.searchBindUserList()
|
|
|
|
|
+ },
|
|
|
|
|
+ searchBindUserList() {
|
|
|
|
|
+ this.bindListLoading = true
|
|
|
|
|
+ searchBindUser(this.bindSearchParams).then(response => {
|
|
|
|
|
+ this.bindUserList = response.rows
|
|
|
|
|
+ this.bindTotal = response.total
|
|
|
|
|
+ this.bindListLoading = false
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ this.bindListLoading = false
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ resetBindSearch() {
|
|
|
|
|
+ this.bindSearchParams = { pageNum: 1, pageSize: 10, userId: undefined, nickName: undefined }
|
|
|
|
|
+ this.searchBindUserList()
|
|
|
|
|
+ },
|
|
|
|
|
+ // 通用:单选框限制只能选一个
|
|
|
|
|
+ singleSelect(tableRef, selection) {
|
|
|
|
|
+ if (selection.length > 1) {
|
|
|
|
|
+ const refTable = this.$refs[tableRef]
|
|
|
|
|
+ if (refTable) {
|
|
|
|
|
+ refTable.clearSelection()
|
|
|
|
|
+ refTable.toggleRowSelection(selection[selection.length - 1])
|
|
|
|
|
+ }
|
|
|
|
|
+ return selection[selection.length - 1]
|
|
|
|
|
+ }
|
|
|
|
|
+ return selection.length === 1 ? selection[0] : null
|
|
|
|
|
+ },
|
|
|
|
|
+ // 手动发课:会员勾选
|
|
|
|
|
+ handleManualMemberSelect(selection) {
|
|
|
|
|
+ this.selectedBindUser = this.singleSelect('manualMemberTable', selection)
|
|
|
|
|
+ },
|
|
|
|
|
+ // 自动发课:会员列表勾选
|
|
|
|
|
+ handleAutoMemberSelect(selection) {
|
|
|
|
|
+ this.selectedAutoMember = this.singleSelect('autoMemberTable', selection)
|
|
|
|
|
+ },
|
|
|
|
|
+ // 自动发课:外部联系人勾选
|
|
|
|
|
+ handleExternalSelect(selection) {
|
|
|
|
|
+ this.selectedExternalContact = this.singleSelect('externalContactTable', selection)
|
|
|
|
|
+ },
|
|
|
|
|
+ confirmBind() {
|
|
|
|
|
+ const isAuto = this.bindMode === 'auto'
|
|
|
|
|
+ if (isAuto && (!this.selectedAutoMember || !this.selectedExternalContact)) {
|
|
|
|
|
+ this.$message.warning('请分别勾选一个会员和一个外部联系人')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!isAuto && !this.selectedBindUser) {
|
|
|
|
|
+ this.$message.warning('请选择一个用户')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.bindConfirmLoading = true
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ bindId: this.currentBindRow.id
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isAuto) {
|
|
|
|
|
+ params.bindUserId = this.selectedAutoMember.userId
|
|
|
|
|
+ params.externalContactId = this.selectedExternalContact.id
|
|
|
|
|
+ } else {
|
|
|
|
|
+ params.bindUserId = this.selectedBindUser.userId
|
|
|
|
|
+ params.projectId = this.projectId
|
|
|
|
|
+ }
|
|
|
|
|
+ bindWxUser(params).then(response => {
|
|
|
|
|
+ this.$message.success(response.msg || '绑定成功')
|
|
|
|
|
+ this.bindDialogVisible = false
|
|
|
|
|
+ this.bindConfirmLoading = false
|
|
|
|
|
+ this.getList()
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ this.bindConfirmLoading = false
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ handleUnbind(row) {
|
|
|
|
|
+ this.$confirm('确认解绑该用户的微信绑定信息?', '提示', {
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
|
+ type: 'warning'
|
|
|
|
|
+ }).then(() => {
|
|
|
|
|
+ unbindWxUser(row.id).then(response => {
|
|
|
|
|
+ this.$message.success(response.msg || '解绑成功')
|
|
|
|
|
+ this.getList()
|
|
|
|
|
+ })
|
|
|
|
|
+ }).catch(() => {})
|
|
|
|
|
+ },
|
|
|
|
|
+ handleShowDetail(row) {
|
|
|
|
|
+ this.currentDetailRow = row
|
|
|
|
|
+ this.boundUserDetail = null
|
|
|
|
|
+ this.detailDialogVisible = true
|
|
|
|
|
+ // 查询微信用户详情
|
|
|
|
|
+ if (row.bindUserId) {
|
|
|
|
|
+ this.detailLoading = true
|
|
|
|
|
+ getBoundUserInfo(row.bindUserId).then(response => {
|
|
|
|
|
+ this.boundUserDetail = response.data
|
|
|
|
|
+ this.detailLoading = false
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ this.detailLoading = false
|
|
|
|
|
+ })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.detailLoading = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // 外部联系人列表
|
|
|
|
|
+ searchExternalContactList() {
|
|
|
|
|
+ this.externalListLoading = true
|
|
|
|
|
+ searchExternalContact(this.externalContactSearchParams).then(response => {
|
|
|
|
|
+ this.externalContactList = response.rows
|
|
|
|
|
+ this.externalTotal = response.total
|
|
|
|
|
+ this.externalListLoading = false
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ this.externalListLoading = false
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ resetExternalSearch() {
|
|
|
|
|
+ this.externalContactSearchParams = { pageNum: 1, pageSize: 10, name: undefined, corpName: undefined }
|
|
|
|
|
+ this.searchExternalContactList()
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.bind-mode-switch {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ padding: 12px 16px;
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+}
|
|
|
|
|
+.bind-mode-label {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ margin-right: 12px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+.bind-section {
|
|
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+.bind-section-title {
|
|
|
|
|
+ padding: 10px 16px;
|
|
|
|
|
+ background: #fafafa;
|
|
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+}
|
|
|
|
|
+.bind-section-title i {
|
|
|
|
|
+ margin-right: 6px;
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+}
|
|
|
|
|
+.bind-section .el-form {
|
|
|
|
|
+ padding: 12px 16px 0;
|
|
|
|
|
+}
|
|
|
|
|
+.bind-section .el-table {
|
|
|
|
|
+ border-top: none;
|
|
|
|
|
+ border-left: none;
|
|
|
|
|
+ border-right: none;
|
|
|
|
|
+}
|
|
|
|
|
+.bind-section .pagination-container {
|
|
|
|
|
+ padding: 0 16px;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|