Browse Source

2024.12.25 - 人脉银行功能完善

GuYun-D 3 months ago
parent
commit
60310c4c43

+ 74 - 11
src/api/rm-bank/index.js

@@ -39,47 +39,110 @@ export function deleteClubApi(params) {
 }
 
 // 获取俱乐部详情
-export function getClubDetailApi(params){
+export function getClubDetailApi(params) {
   return request({
     url: '/people-bank-club/getClubById',
-    method: "GET",
+    method: 'GET',
     params
   })
 }
 
 // 设置会员为管理
-export function patchClubMemberIsAdminApi(params){
+export function patchClubMemberIsAdminApi(params) {
   return request({
     url: '/people-bank-club-member/patchClubMemberIsAdmin',
-    method: "PATCH",
+    method: 'PATCH',
     params
   })
 }
 
 // 获取会员列表
-export function getClubMemberApi(params){
+export function getClubMemberApi(params) {
   return request({
     url: '/people-bank-member/getMemberAll',
-    method: "GET",
+    method: 'GET',
     params
   })
 }
 
-// 获取活动列表   
+// 获取活动列表
 // 获取会员列表
-export function getActivitiesAllApi(params){
+export function getActivitiesAllApi(params) {
   return request({
     url: '/people-bank-activities/getActivitiesAll',
-    method: "GET",
+    method: 'GET',
     params
   })
 }
 
 // 获取活动详情
-export function getActivityDetailApi(params){
+export function getActivityDetailApi(params) {
   return request({
     url: '/people-bank-activities/getActivitiesById',
-    method: "GET",
+    method: 'GET',
+    params
+  })
+}
+
+// 修改会员信息
+export function editMemberApi(data) {
+  return request({
+    url: '/people-bank-member/patchMember',
+    method: 'PATCH',
+    data
+  })
+}
+
+// 创建活动
+export function createActivityApi(data) {
+  return request({
+    url: '/people-bank-activities/postPeopleBankActivities',
+    method: 'POST',
+    data
+  })
+}
+
+// 创建活动
+export function deleteActivityApi(params) {
+  return request({
+    url: '/people-bank-activities/deletePeopleBankActivitiesById',
+    method: 'DELETE',
+    params
+  })
+}
+
+// 修改活动信息
+export function patchPeopleBankActivitiesByIdApi(data) {
+  return request({
+    url: '/people-bank-activities/patchPeopleBankActivitiesById',
+    method: 'PATCH',
+    data
+  })
+}
+
+// 获取会员详情
+export function getMemberByMemberIdApi(params) {
+  return request({
+    url: '/people-bank-member/getMemberByMemberId',
+    method: 'GET',
+    params
+  })
+}
+
+// 修改会员在俱乐部中的信息
+export function patchClubMemberApi(data) {
+  return request({
+    url: '/people-bank-club-member/patchClubMember',
+    method: 'PATCH',
+    data
+  })
+}
+
+// 获取会员身份列表
+export function getClubIdentityListApi(params) {
+  return request({
+    url: '/people-bank-club-member/getClubIdentityList',
+    method: 'GET',
     params
   })
 }

+ 10 - 0
src/styles/element-ui.scss

@@ -70,3 +70,13 @@
     }
   }
 }
+
+.upload-video {
+  .el-upload {
+    width: 100% !important;
+    height: 100% !important;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+}

+ 429 - 0
src/views/rm-bank/activity/components/ActivityModal.vue

@@ -0,0 +1,429 @@
+<template>
+  <el-dialog :close-on-click-modal="false" title="提示" :visible.sync="activityVisible" width="50%">
+    <el-steps align-center :active="currentStep" finish-status="success" style="margin-bottom: 10px">
+      <el-step title="基本信息"></el-step>
+      <el-step title="其他信息"></el-step>
+    </el-steps>
+
+    <el-carousel :height="heightInfo.swiperHeight + 'px'" ref="elCarouselRef" :autoplay="false" indicator-position="none" arrow="never">
+      <el-carousel-item name="step-one">
+        <el-form ref="activityModalFormRef" :model="activityForm" :rules="activityRules.oneRules" label-width="auto">
+          <el-form-item label="请选择俱乐部" prop="club.id" v-if="!activityForm.activities.id">
+            <el-select style="width: 100%" v-model="activityForm.club.id" placeholder="请选择">
+              <el-option v-for="item in clubList" :key="item.id" :label="item.name" :value="item.id"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="活动类型" prop="activities.typeEnum">
+            <el-select style="width: 100%" v-model="activityForm.activities.typeEnum" placeholder="请选择活动类型" @change="handleChangeTypeState">
+              <el-option v-for="item in ACTIVITY_TYPE_INFO" :key="item.typeId" :label="item.name" :value="item.enumKey"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="参与人数" v-if="!NOT_LUAN_QI_BA_ZAO.includes(activityForm.activities.typeEnum)" prop="activities.peopleNumber">
+            <el-input :min="1" placeholder="请输入参与人数" type="number" v-model.number="activityForm.activities.peopleNumber"></el-input>
+          </el-form-item>
+          <el-form-item label="参与用户范围" prop="activities.registrationScope" v-if="!NOT_LUAN_QI_BA_ZAO.includes(activityForm.activities.typeEnum)">
+            <el-select style="width: 100%" v-model="activityForm.activities.registrationScope" placeholder="请选择参与用户范围">
+              <el-option label="所有用户" :value="1"></el-option>
+              <el-option label="仅限俱乐部会员" :value="2"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="活动标题" prop="activities.title">
+            <el-input placeholder="请输入活动标题" v-model="activityForm.activities.title"></el-input>
+          </el-form-item>
+
+          <el-form-item :label="startTimeLabel" prop="activities.startTime">
+            <el-date-picker
+              value-format="yyyy-MM-dd HH:mm:ss"
+              style="width: 100%"
+              v-model="activityForm.activities.startTime"
+              type="datetime"
+              :placeholder="`请选择${startTimeLabel}`"
+              align="right"
+            ></el-date-picker>
+          </el-form-item>
+
+          <el-form-item v-if="!NOT_LUAN_QI_BA_ZAO.includes(activityForm.activities.typeEnum)" label="结束时间" prop="activities.endTime">
+            <el-date-picker
+              @change="handleSelectEndTime"
+              :disabled="!activityForm.activities.startTime"
+              value-format="yyyy-MM-dd HH:mm:ss"
+              style="width: 100%"
+              v-model="activityForm.activities.endTime"
+              type="datetime"
+              placeholder="请选择结束时间"
+              align="right"
+            ></el-date-picker>
+          </el-form-item>
+
+          <el-form-item
+            v-if="!NOT_LUAN_QI_BA_ZAO.includes(activityForm.activities.typeEnum)"
+            label="地址"
+            prop="activities.activitiesAddress"
+            :rules="[{ required: true, message: '请选择地址', trigger: 'change' }]"
+          >
+            <el-cascader
+              v-show="isEditAdddress || !activityForm.activities.id"
+              v-model="activityForm.activities.activitiesAddress"
+              style="width: 100%"
+              :props="areaData"
+              ref="cascaderRef"
+              @change="handleCascaderChange"
+            ></el-cascader>
+            <div :style="{ display: isEditAdddress ? 'none' : 'flex' }" v-if="activityForm.activities.id">
+              <el-input readonly :value="activityForm.activities.activitiesAddress"></el-input>
+              <el-button style="margin-left: 20px" type="primary" size="mini" @click="isEditAdddress = true">修改区域</el-button>
+            </div>
+          </el-form-item>
+
+          <el-form-item
+            v-if="!NOT_LUAN_QI_BA_ZAO.includes(activityForm.activities.typeEnum)"
+            label="详细地址"
+            prop="activities.activitiesDetailAddress"
+            :rules="[{ required: true, message: '请输入详细地址', trigger: 'blur' }]"
+          >
+            <el-input type="textarea" placeholder="请输入详细地址" v-model="activityForm.activities.activitiesDetailAddress"></el-input>
+          </el-form-item>
+
+          <el-form-item label="发布人昵称" prop="activities.publishMemberName">
+            <el-input placeholder="请输入发布人昵称" v-model="activityForm.activities.publishMemberName"></el-input>
+          </el-form-item>
+
+          <el-form-item label="发布人头像" prop="activities.publishMemberAvatar">
+            <ImageUpload v-model="activityForm.activities.publishMemberAvatar" :limit="1"></ImageUpload>
+          </el-form-item>
+
+          <el-form-item
+            :rules="[{ required: true, message: '请选择商机类型', trigger: 'change' }]"
+            label="商机类型"
+            prop="activities.opportunityType"
+            v-if="activityForm.activities.typeEnum === BUSINESS_ACTIVITY_TYPE"
+          >
+            <el-select style="width: 100%" v-model="activityForm.activities.opportunityType" placeholder="请选择商机类型">
+              <el-option label="寻求合作" :value="1"></el-option>
+              <el-option label="寻求采购" :value="2"></el-option>
+            </el-select>
+          </el-form-item>
+        </el-form>
+      </el-carousel-item>
+
+      <el-carousel-item name="step-two">
+        <el-form ref="activityNextModalFormRef" label-width="auto" :model="activityForm" :rules="activityRules.twoRules">
+          <el-form-item v-if="NOTICE_ACTIVITY_TYPE !== activityForm.activities.typeEnum" label="封面" prop="activities.cover">
+            <ImageUpload v-model="activityForm.activities.cover" :limit="1"></ImageUpload>
+          </el-form-item>
+
+          <el-form-item v-if="NOTICE_ACTIVITY_TYPE !== activityForm.activities.typeEnum" label="宣传视频" prop="activities.promotionalVideo">
+            <el-upload
+              style="width: 200px; height: 100px; border-radius: 5px; background-color: #fbfdff; border: 1px dashed #c0ccda; display: flex; align-items: center; justify-content: center"
+              :action="uploadUrl"
+              :show-file-list="false"
+              :on-success="handleUplaodVideoSuccess"
+              :before-upload="hanldeBeforeUploadVideo"
+              class="upload-video"
+            >
+              <video style="width: 200px; height: 100px" controls v-if="activityForm.activities.promotionalVideo" :src="activityForm.activities.promotionalVideo"></video>
+            </el-upload>
+          </el-form-item>
+
+          <el-form-item label="内容" prop="activities.content">
+            <div class="editor-wrapper" v-loading.lock="editorCustomOptions.showLoading">
+              <quillEditor ref="quillEditorRef" v-model="activityForm.activities.content"></quillEditor>
+            </div>
+          </el-form-item>
+
+          <el-form-item v-if="BUSINESS_ACTIVITY_TYPE !== activityForm.activities.typeEnum" label="联系方式" prop="activities.opportunityContactNumber">
+            <el-input placeholder="请填写商机联系方式" v-model="activityForm.activities.opportunityContactNumber"></el-input>
+          </el-form-item>
+
+          <el-form-item v-if="BUSINESS_ACTIVITY_TYPE !== activityForm.activities.typeEnum" label="邮箱" prop="activities.opportunityContactEmail">
+            <el-input placeholder="请填写商机联系邮箱" v-model="activityForm.activities.opportunityContactEmail"></el-input>
+          </el-form-item>
+
+          <el-form-item v-if="BUSINESS_ACTIVITY_TYPE !== activityForm.activities.typeEnum" label="公司广网链接" prop="activities.participationMethod">
+            <el-input placeholder="请填写公司官网链接" v-model="activityForm.activities.participationMethod"></el-input>
+          </el-form-item>
+
+          <el-form-item v-if="NOTICE_ACTIVITY_TYPE !== activityForm.activities.typeEnum" label="参与方式" prop="activities.opportunityAuthorityUrl">
+            <el-input :rows="3" type="textarea" placeholder="请填写参与方式" v-model="activityForm.activities.opportunityAuthorityUrl"></el-input>
+          </el-form-item>
+
+          <el-form-item v-if="NOTICE_ACTIVITY_TYPE !== activityForm.activities.typeEnum" label="注意事项" prop="activities.precautions">
+            <el-input :rows="3" type="textarea" placeholder="请填写参与方式" v-model="activityForm.activities.precautions"></el-input>
+          </el-form-item>
+        </el-form>
+      </el-carousel-item>
+    </el-carousel>
+
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="close">取消</el-button>
+        <el-button type="info" v-if="currentStep === 0" @click="handleNextStep">下一步</el-button>
+        <el-button type="info" v-if="currentStep === 1" @click="handlePrev">上一步</el-button>
+        <el-button @click="handleOpActivity(ACTIVITY_STATUS_INFO[0].enumKey)" v-if="currentStep === 1" :loading="isLoading === ACTIVITY_STATUS_INFO[0].enumKey">存为草稿</el-button>
+        <el-button @click="handleOpActivity(ACTIVITY_STATUS_INFO[1].enumKey)" v-if="currentStep === 1" :loading="isLoading === ACTIVITY_STATUS_INFO[1].enumKey">
+          {{ activityForm.activities.id ? '确认编辑' : '创建并发布' }}
+        </el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+import { quillEditor } from 'vue-quill-editor'
+import ImageUpload from '@/components/ImageUpload'
+import { uploadUrl } from '@/utils/request'
+import { getClubListApi, createActivityApi, patchPeopleBankActivitiesByIdApi } from '@/api/rm-bank'
+import { getProvinceList, getChildAreaList } from '@/api/address'
+import {
+  ACTIVITY_STATUS_INFO,
+  listenEditorContentHeightChange,
+  getCurrentActivityRules,
+  getDefaultActivityForm,
+  ACTIVITY_TYPE_INFO,
+  NOTICE_ACTIVITY_TYPE,
+  BUSINESS_ACTIVITY_TYPE,
+  interceptQuillImageUpload,
+  NOT_LUAN_QI_BA_ZAO,
+  DEFAULT_ACTIVITY_ADDRESS,
+  getTypeEnumByValue,
+  getStateEnumByValue
+} from './utils'
+
+// 引入 editor 样式
+import 'quill/dist/quill.core.css'
+import 'quill/dist/quill.snow.css'
+import 'quill/dist/quill.bubble.css'
+
+export default {
+  components: {
+    quillEditor,
+    ImageUpload
+  },
+  data() {
+    return {
+      NOTICE_ACTIVITY_TYPE,
+      ACTIVITY_TYPE_INFO,
+      BUSINESS_ACTIVITY_TYPE,
+      ACTIVITY_STATUS_INFO,
+      activityVisible: false,
+      activityForm: getDefaultActivityForm(),
+      clubList: [],
+      editorCustomOptions: {
+        showLoading: false
+      },
+      isLoading: false,
+      currentStep: 0,
+      heightInfo: {
+        swiperHeight: 0
+      },
+      NOT_LUAN_QI_BA_ZAO,
+      isEditAdddress: false,
+      selectAddressLabel: '',
+      areaData: {
+        lazy: true,
+        label: 'name',
+        value: 'id',
+        lazyLoad(node, resolve) {
+          const { level, value } = node
+          if (level === 0) {
+            getProvinceList().then((res) => {
+              resolve(res.data)
+            })
+          } else if (level != 0) {
+            getChildAreaList(value).then((res) => {
+              resolve(
+                res.data.map((item) => {
+                  item.leaf = level === 3
+                  return item
+                })
+              )
+            })
+          }
+        }
+      },
+      uploadUrl,
+      uploadVideoLoading: false,
+      stopObserver: null
+    }
+  },
+  mounted() {
+    // 获取俱乐部列表
+    this.getClubList()
+  },
+  methods: {
+    show(row) {
+      if (row) {
+        const activitiesAddress = row.activitiesAddress !== DEFAULT_ACTIVITY_ADDRESS ? row.activitiesAddress : ''
+        let activitiesAddressTemp = ''
+        let activitiesDetailAddressTemp = ''
+        if (activitiesAddress) {
+          activitiesAddressTemp = (activitiesAddress.split(' ')[0] || '').replace('undefined', '')
+          activitiesDetailAddressTemp = (activitiesAddress.split(' ')[1] || '').replace('undefined', '')
+          this.selectAddressLabel = (activitiesAddress.split(' ')[0] || '').replace('undefined', '')
+        }
+        Object.assign(this.activityForm.activities, row, {
+          activitiesAddress: activitiesAddressTemp,
+          activitiesDetailAddress: activitiesDetailAddressTemp,
+          id: row.id,
+          typeEnum: getTypeEnumByValue(Number(row.type)),
+          stateEnum: getStateEnumByValue(Number(row.state))
+        })
+        this.handleChangeTypeState()
+      }
+      this.activityVisible = true
+      setTimeout(this.calcSwiperHeight)
+      // 拦截editor的图片上传 转码 -> 服务器上传
+      // 监听输入框的高度变化,然后动态修改轮播图的高度
+      this.$nextTick(() => {
+        interceptQuillImageUpload(this, this.editorCustomOptions)
+        this.stopObserver = listenEditorContentHeightChange(this, this.calcSwiperHeight)
+      })
+    },
+
+    // 获取俱乐部列表
+    async getClubList() {
+      const res = await getClubListApi({ page: 1, pageSize: 100 })
+      this.clubList = res.data.list
+    },
+
+    // 点击下一步
+    async handleNextStep() {
+      await this.$refs.activityModalFormRef.validate()
+      this.$refs.elCarouselRef.next()
+      this.currentStep = 1
+      this.calcSwiperHeight()
+    },
+
+    // 点击上一步
+    handlePrev() {
+      this.$refs.elCarouselRef.prev()
+      this.currentStep = 0
+      this.calcSwiperHeight()
+    },
+
+    // 点击完成
+    async handleOpActivity(status) {
+      try {
+        this.isLoading = status
+        await this.$refs.activityNextModalFormRef.validate()
+        const data = this.transformData(status)
+        data.activities.id && delete data.club
+        const api = data.activities.id ? patchPeopleBankActivitiesByIdApi : createActivityApi
+        await api(data)
+        this.$message.success(data.activities.id ? '编辑成功' : '操作成功')
+        this.close()
+        this.$emit('refresh')
+      } catch (error) {
+      } finally {
+        this.isLoading = ''
+      }
+    },
+
+    close() {
+      this.activityForm = getDefaultActivityForm()
+      this.currentStep = 0
+      this.$refs.elCarouselRef.setActiveItem('step-one')
+      this.$refs.activityModalFormRef.clearValidate()
+      this.$refs.activityNextModalFormRef.clearValidate()
+      this.$refs.activityModalFormRef.resetFields()
+      this.$refs.activityNextModalFormRef.resetFields()
+      this.selectAddressLabel = ''
+      typeof this.stopObserver === 'function' && this.stopObserver()
+      this.$nextTick(() => {
+        this.activityVisible = false
+      })
+    },
+
+    // 计算swiper的高度
+    calcSwiperHeight() {
+      this.$nextTick(() => {
+        const refName = this.currentStep === 0 ? 'activityModalFormRef' : 'activityNextModalFormRef'
+        const containerRef = this.$refs[refName]
+        if (!containerRef) return
+        const { height } = containerRef.$el.getBoundingClientRect()
+        this.heightInfo.swiperHeight = height
+      })
+    },
+
+    // 活动类型发生了变化
+    handleChangeTypeState() {
+      this.activityForm = getDefaultActivityForm(true, { ...this.activityForm })
+      // 拦截editor的图片上传 转码 -> 服务器上传
+      // 监听输入框的高度变化,然后动态修改轮播图的高度
+      this.calcSwiperHeight()
+    },
+
+    handleCascaderChange() {
+      const checkedNode = this.$refs.cascaderRef.getCheckedNodes()
+      const { pathLabels } = checkedNode[0]
+      this.selectAddressLabel = pathLabels.join('')
+    },
+
+    // 选择了结束时间
+    handleSelectEndTime() {
+      const startTime = new Date(this.activityForm.activities.startTime)
+      const endTime = new Date(this.activityForm.activities.endTime)
+      if (endTime - startTime <= 0) {
+        this.$message.warning('结束时间不能早于开始时间')
+        this.activityForm.activities.endTime = ''
+      }
+    },
+
+    // 转化数据
+    transformData(activityStatus) {
+      if (!activityStatus) {
+        throw new Error('请填写活动发布状态')
+      }
+      const data = JSON.parse(JSON.stringify(this.activityForm))
+      data.activities.stateEnum = activityStatus
+
+      if (NOT_LUAN_QI_BA_ZAO.includes(data.activities.typeEnum)) {
+        data.activities.activitiesAddress = DEFAULT_ACTIVITY_ADDRESS
+      } else {
+        data.activities.activitiesAddress = this.selectAddressLabel
+        if (data.activities.activitiesDetailAddress) {
+          data.activities.activitiesAddress = data.activities.activitiesAddress + ' ' + data.activities.activitiesDetailAddress
+        }
+      }
+
+      delete data.activities.activitiesDetailAddress
+      return data
+    },
+
+    // 上传视频之前
+    hanldeBeforeUploadVideo(file) {
+      const isVideo = file.type.startsWith('video/')
+      const isLimitSize = file.size / 1024 / 1024 < 50 // 转换为MB
+      if (!isVideo) {
+        this.$message.error('上传文件必须是视频文件!')
+      }
+      if (!isLimitSize) {
+        this.$message.error('上传的视频大小不能超过 50MB!')
+      }
+      return isVideo && isLimitSize
+    },
+
+    // 上传视频完成之后
+    handleUplaodVideoSuccess(e) {
+      if (!e.code) {
+        this.activityForm.activities.promotionalVideo = e.data.url
+      }
+    }
+  },
+
+  computed: {
+    startTimeLabel() {
+      const DEFAULT_LABEL = '开始时间'
+      const publishTimeType = [NOTICE_ACTIVITY_TYPE, BUSINESS_ACTIVITY_TYPE]
+      const { typeEnum } = this.activityForm.activities
+      return typeEnum ? (publishTimeType.includes(typeEnum) ? '发布时间' : DEFAULT_LABEL) : DEFAULT_LABEL
+    },
+
+    activityRules() {
+      return getCurrentActivityRules()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 20 - 4
src/views/rm-bank/activity/components/DetailModal.vue

@@ -34,9 +34,8 @@
         <el-descriptions-item>
           <template slot="label">状态</template>
           <el-tag type="primary" size="mini" v-if="activityInfo.state == 2">已开始</el-tag>
-          <el-tag type="warning" size="mini" v-if="activityInfo.state == 4">已结束</el-tag>
-          <el-tag type="success" size="mini" v-if="activityInfo.state == 4">已结束</el-tag>
-          <el-tag type="info" size="mini" v-if="activityInfo.state == 5">已撤销</el-tag>
+          <el-tag type="success" size="mini" v-if="activityInfo.state == 3">已结束</el-tag>
+          <el-tag type="info" size="mini" v-if="activityInfo.state == 4">已撤销</el-tag>
           <el-tag type="danger" size="mini" v-if="activityInfo.state == 1">草稿</el-tag>
         </el-descriptions-item>
 
@@ -47,8 +46,17 @@
         </el-descriptions-item>
 
         <el-descriptions-item>
+          <template slot="label">封面</template>
+          <el-image style="width: 40px; height: 40px; border-radius: 50%" v-if="activityInfo.cover" :src="activityInfo.cover" :preview-src-list="[activityInfo.cover]" fit="cover" />
+          <span v-else>--</span>
+        </el-descriptions-item>
+
+        <el-descriptions-item>
           <template slot="label">宣传视频</template>
-          <video style="width: 100px; height: 50px" :src="activityInfo.promotionalVideo"></video>
+          <!-- <video style="width: 100px; height: 50px" :src="activityInfo.promotionalVideo"></video> -->
+
+          <el-button @click="$refs.previewVideoRef && $refs.previewVideoRef.show(activityInfo.promotionalVideo)" v-if="activityInfo.promotionalVideo" type="text">查看视频</el-button>
+          <span v-else>--</span>
         </el-descriptions-item>
 
         <el-descriptions-item>
@@ -147,13 +155,17 @@
         <el-button size="mini" @click="close">取消</el-button>
       </span>
     </template>
+
+    <PreviewVideo ref="previewVideoRef"></PreviewVideo>
   </el-dialog>
 </template>
 
 <script>
 import { getActivityDetailApi } from '@/api/rm-bank/index'
+import PreviewVideo from './PreviewVideo.vue'
 
 export default {
+  components: {PreviewVideo},
   data() {
     return {
       isLoading: false,
@@ -193,6 +205,10 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+::v-deep .el-descriptions-item__cell {
+  width: 50px;
+}
+
 .margin-top {
   margin-top: 20px;
 }

+ 27 - 0
src/views/rm-bank/activity/components/PreviewVideo.vue

@@ -0,0 +1,27 @@
+<template>
+  <el-dialog append-to-body :close-on-click-modal="false" title="视频预览" :visible.sync="previewVideoVisible" width="20%">
+    <video autoplay  style="width: 100%" :src="videoUrl" controls></video>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button size="mini" @click="previewVideoVisible = false">取消</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      previewVideoVisible: false
+    }
+  },
+
+  methods: {
+    show(videoUrl) {
+      this.videoUrl = videoUrl
+      this.previewVideoVisible = true
+    }
+  }
+}
+</script>

+ 202 - 0
src/views/rm-bank/activity/components/utils.js

@@ -0,0 +1,202 @@
+import axios from 'axios'
+import { uploadUrl } from '@/utils/request'
+
+// 活动类型
+export const NOTICE_ACTIVITY_TYPE = 'NOTICE' // 公告的活动类型
+export const BUSINESS_ACTIVITY_TYPE = 'BUSINESS' // 商机的活动类型
+
+// 不需要结束时间/等乱七八糟的
+export const NOT_LUAN_QI_BA_ZAO = [NOTICE_ACTIVITY_TYPE, BUSINESS_ACTIVITY_TYPE]
+
+const DEFAULT_PEOPLE_NUMBER = 10000
+const DEFAULT_END_TIME = '2099-01-31 00:00:00'
+export const DEFAULT_ACTIVITY_ADDRESS = 'xxxxxxxxxxxxxxxxx'
+
+// 活动类型
+export const ACTIVITY_TYPE_INFO = [
+  { name: '公益活动', enumKey: 'PUBLIC', typeKey: 1 },
+  { name: '会议', enumKey: 'MEETING', typeKey: 2 },
+  { name: '爱心传递', enumKey: 'LOVE', typeKey: 3 },
+  { name: '公告', enumKey: NOTICE_ACTIVITY_TYPE, typeKey: 4 },
+  { name: '商机', enumKey: BUSINESS_ACTIVITY_TYPE, typeKey: 5 }
+]
+
+// 活动状态
+// !内容顺序不要更改
+export const ACTIVITY_STATUS_INFO = [
+  { name: '草稿', enumKey: 'DRAFT', typeKey: 1 },
+  { name: '已开始', enumKey: 'STARTED', typeKey: 2 },
+  { name: '已结束', enumKey: 'END', typeKey: 3 },
+  { name: '已撤销', enumKey: 'CANCELLED', typeKey: 4 }
+]
+
+/**
+ * @description 获取创建活动的默认表单数据
+ * @returns
+ */
+
+export const getDefaultActivityForm = (isMerge = false, originData = {}) => {
+  const defaultActivity = {
+    id: undefined,
+    typeEnum: NOTICE_ACTIVITY_TYPE,
+    title: '',
+    cover: '',
+    content: '',
+    peopleNumber: DEFAULT_PEOPLE_NUMBER,
+    stateEnum: '',
+    registrationScope: 1,
+    promotionalVideo: '',
+    startTime: '',
+    endTime: DEFAULT_END_TIME,
+    activitiesAddress: DEFAULT_ACTIVITY_ADDRESS,
+    participationMethod: '',
+    activitiesDetailAddress: '',
+    precautions: '',
+    opportunityType: '',
+    opportunityContactNumber: '',
+    opportunityContactEmail: '',
+    opportunityAuthorityUrl: '',
+    publishMemberName: '',
+    publishMemberAvatar: ''
+  }
+
+  if (isMerge) {
+    const { club, activities } = originData
+    return {
+      club: {
+        id: club.id || undefined
+      },
+      activities: {
+        ...defaultActivity,
+        ...activities,
+        typeEnum: activities.typeEnum || defaultActivity.typeEnum,
+        peopleNumber: NOT_LUAN_QI_BA_ZAO.includes(activities.typeEnum) ? DEFAULT_PEOPLE_NUMBER : activities.peopleNumber !== DEFAULT_PEOPLE_NUMBER ? activities.peopleNumber : '',
+        endTime: NOT_LUAN_QI_BA_ZAO.includes(activities.typeEnum) ? DEFAULT_END_TIME : activities.endTime !== DEFAULT_END_TIME ? activities.endTime : '',
+        activitiesAddress: NOT_LUAN_QI_BA_ZAO.includes(activities.typeEnum) ? DEFAULT_ACTIVITY_ADDRESS : activities.activitiesAddress !== DEFAULT_ACTIVITY_ADDRESS ? activities.activitiesAddress : ''
+      }
+    }
+  }
+
+  return {
+    club: { id: undefined },
+    activities: defaultActivity
+  }
+}
+
+/**
+ * @description 获取当前表单校验rules
+ */
+export const getCurrentActivityRules = (typeEnum) => {
+  const baseOneRules = {
+    'club.id': [{ required: true, message: '请选择俱乐部', trigger: 'change' }],
+    'activities.typeEnum': [{ required: true, message: '请选择类型', trigger: 'change' }],
+    'activities.title': [{ required: true, message: '请填写标题', trigger: 'blur' }],
+    'activities.startTime': [{ required: true, message: '请选择开始时间', trigger: 'blur' }],
+    'activities.endTime': [{ required: true, message: '请选择结束时间', trigger: 'blur' }],
+    'activities.registrationScope': [{ required: true, message: '请选择范围', trigger: 'blur' }],
+    'activities.peopleNumber': [{ required: true, message: '请填写参与人数', trigger: 'blur' }]
+  }
+
+  const baseTwoRules = {
+    'activities.content': [{ required: true, message: '请输入内容', trigger: 'blur' }]
+  }
+
+  return { oneRules: baseOneRules, twoRules: baseTwoRules }
+}
+
+/**
+ * @description 拦截quill的图片上传操作
+ * @param {VueInstance} ctx 当前页面实例
+ * @param {Object} options 自定义配置
+ * @param {boolean} options.showLoading 是否要显示图片上传的加载蒙版
+ *
+ */
+export const interceptQuillImageUpload = (ctx, options = {}) => {
+  const quill = ctx.$refs.quillEditorRef.quill
+  if (!quill) return
+
+  const _customImageUpload = async (file) => {
+    const formData = new FormData()
+    formData.append('file', file)
+    const res = await axios.post(uploadUrl, formData)
+    if (!res.data.code) {
+      return res.data.data.url
+    } else {
+      ctx.$message.error('图片上传失败')
+      return undefined
+    }
+  }
+
+  const _onImageUpload = () => {
+    const input = document.createElement('input')
+    input.setAttribute('type', 'file')
+    input.setAttribute('accept', 'image/*')
+
+    input.onchange = async () => {
+      const file = input.files[0]
+
+      if (file) {
+        try {
+          options.showLoading = true
+          const allowedExtensions = /(\.png|\.jpg|\.jpeg)$/i
+          if (!allowedExtensions.exec(file.name)) {
+            return ctx.$message.error('上传的文件格式不正确,请选择 .png, .jpg 或 .jpeg 格式的文件。')
+          }
+          // 检查文件大小
+          const maxSize = 5 * 1024 * 1024
+          if (file.size > maxSize) {
+            return ctx.$message.error('文件大小不能超过 5MB。')
+          }
+          const imageUrl = await _customImageUpload(file)
+          const range = quill.getSelection(true)
+          quill.insertEmbed(range.index, 'image', imageUrl)
+          quill.setSelection(range.index + 1)
+          ctx.$message.success('图片上传成功')
+        } catch (error) {
+          ctx.$message.error('图片上传失败')
+        } finally {
+          options.showLoading = false
+        }
+      }
+    }
+
+    input.click()
+  }
+
+  const toolbar = quill.getModule('toolbar')
+  toolbar.addHandler('image', _onImageUpload)
+}
+
+/**
+ * @description 监听 输入框的高度变化,然后动态修改走马灯的高度
+ */
+export const listenEditorContentHeightChange = (ctx, calcHeightFn) => {
+  if (!ctx || !calcHeightFn) {
+    return console.error('this and calcHeightFn is required')
+  }
+  const quill = ctx.$refs.quillEditorRef.quill
+  if (!quill) return
+  const editorElement = ctx.$refs.quillEditorRef.$el.querySelector('.ql-editor')
+  let prevHeight = 0
+  const observer = new MutationObserver(() => {
+    const currentHeight = editorElement.clientHeight
+    if (currentHeight > prevHeight) {
+      prevHeight = currentHeight
+      typeof calcHeightFn === 'function' && calcHeightFn(currentHeight)
+    }
+  })
+  observer.observe(editorElement, { childList: true, subtree: true, characterData: true })
+  return () => {
+    observer.disconnect(editorElement)
+  }
+}
+
+export const getTypeEnumByValue = (value) => {
+  if (!value) return ''
+  return (ACTIVITY_TYPE_INFO.find((item) => item.typeKey === value) || {}).enumKey || ''
+}
+
+export const getStateEnumByValue = (value) => {
+  if (!value) return ''
+  return (ACTIVITY_STATUS_INFO.find((item) => item.typeKey === value) || {}).enumKey || ''
+}

+ 86 - 21
src/views/rm-bank/activity/index.vue

@@ -18,6 +18,12 @@
           </el-select>
         </el-form-item>
 
+        <el-form-item label="状态">
+          <el-select clearable v-model="query.stateEnum" placeholder="请选择">
+            <el-option v-for="status in ACTIVITY_STATUS_INFO" :label="status.name" :value="status.enumKey" :key="status.enumKey" />
+          </el-select>
+        </el-form-item>
+
         <el-form-item label="商机" v-if="query.typeEnum === 'BUSINESS'">
           <el-select clearable v-model="query.opportunityType" placeholder="请选择">
             <el-option label="寻求合作" value="BUSINESS_TYPE" />
@@ -28,6 +34,7 @@
         <el-form-item>
           <el-button type="primary" plain @click="search">查询</el-button>
           <el-button plain @click="clear">重置</el-button>
+          <el-button type="primary" plain @click="$refs.activityModalRef && $refs.activityModalRef.show()">创建活动</el-button>
         </el-form-item>
       </el-form>
     </div>
@@ -46,39 +53,41 @@
             <el-tag type="danger" size="mini" v-if="scope.row.type == 5">商机</el-tag>
           </template>
         </el-table-column>
+        <el-table-column prop="state" label="状态" align="center" width="120">
+          <template slot-scope="scope">
+            <el-tag type="primary" size="mini" v-if="scope.row.state == 2">已开始</el-tag>
+            <el-tag type="warning" size="mini" v-if="scope.row.state == 3">已结束</el-tag>
+            <el-tag type="info" size="mini" v-if="scope.row.state == 4">已撤销</el-tag>
+            <el-tag type="danger" size="mini" v-if="scope.row.state == 1">草稿</el-tag>
+          </template>
+        </el-table-column>
         <el-table-column prop="title" label="标题" align="center" width="220" />
         <el-table-column prop="avatar" label="封面" align="center" width="70">
           <template slot-scope="scope">
-            <el-image :src="scope.row.cover" style="width: 40px; height: 40px; border-radius: 50%" :preview-src-list="[scope.row.cover]" fit="cover" />
+            <el-image v-if="scope.row.cover" :src="scope.row.cover" style="width: 40px; height: 40px; border-radius: 50%" :preview-src-list="[scope.row.cover]" fit="cover" />
+            <span v-else>--</span>
           </template>
         </el-table-column>
 
         <el-table-column show-overflow-tooltip prop="content" label="活动内容" align="center" width="220" />
         <el-table-column prop="publishMemberId" label="发布人会员ID" align="center" width="120" />
-        <el-table-column prop="publishMemberName" label="发布人昵称" align="center" width="100" />
+        <el-table-column show-overflow-tooltip prop="publishMemberName" label="发布人昵称" align="center" width="100" />
 
         <el-table-column prop="avatar" label="发布人头像" align="center" width="120">
           <template slot-scope="scope">
             <el-image :src="scope.row.publishMemberAvatar" style="width: 40px; height: 40px; border-radius: 50%" :preview-src-list="[scope.row.publishMemberAvatar]" fit="cover" />
           </template>
         </el-table-column>
-        <el-table-column prop="state" label="状态" align="center" width="120">
-          <template slot-scope="scope">
-            <el-tag type="primary" size="mini" v-if="scope.row.state == 2">已开始</el-tag>
-            <el-tag type="warning" size="mini" v-if="scope.row.state == 4">已结束</el-tag>
-            <el-tag type="success" size="mini" v-if="scope.row.state == 4">已结束</el-tag>
-            <el-tag type="info" size="mini" v-if="scope.row.state == 5">已撤销</el-tag>
-            <el-tag type="danger" size="mini" v-if="scope.row.state == 1">草稿</el-tag>
-          </template>
-        </el-table-column>
+
         <el-table-column prop="avatar" label="宣传视频" align="center" width="130">
           <template slot-scope="scope">
-            <video style="width: 100px; height: 50px" :src="scope.row.promotionalVideo"></video>
+            <el-button @click="$refs.previewVideoRef && $refs.previewVideoRef.show(scope.row.promotionalVideo)" v-if="scope.row.promotionalVideo" type="text">查看视频</el-button>
+            <span v-else>--</span>
           </template>
         </el-table-column>
         <el-table-column prop="startTime" label="开始时间" align="center" width="160" />
         <el-table-column prop="endTime" label="结束时间" align="center" width="160" />
-        <el-table-column prop="activitiesAddress" label="活动地址" align="center" width="180" />
+        <el-table-column show-overflow-tooltip prop="activitiesAddress" label="活动地址" align="center" width="180" />
         <el-table-column show-overflow-tooltip prop="participationMethod" label="参与方式" align="center" width="220" />
         <el-table-column show-overflow-tooltip prop="precautions" label="注意事项" align="center" width="220" />
 
@@ -93,11 +102,14 @@
         <el-table-column prop="opportunityContactEmail" label="商机联系邮箱" align="center" width="220" />
         <el-table-column prop="opportunityAuthorityUrl" label="商机官方链接" align="center" width="220" />
 
-        <el-table-column width="100" label="操作" align="center" fixed="right" show-overflow-tooltip>
+        <el-table-column width="240" label="操作" align="left" fixed="right" show-overflow-tooltip>
           <template slot-scope="scope">
-            <div class="btnList">
-               <el-button type="info" size="mini" @click="$refs.detailModalRef && $refs.detailModalRef.show(scope.row.id)">详情</el-button> 
-            </div>
+            <el-button type="info" size="mini" @click="$refs.detailModalRef && $refs.detailModalRef.show(scope.row.id)">详情</el-button>
+            <el-button v-if="[2, 1, 4].includes(scope.row.state * 1)" type="success" size="mini" @click="$refs.activityModalRef && $refs.activityModalRef.show(scope.row)">
+              {{ scope.row.state === 4 ? '重新编辑' : '编辑' }}
+            </el-button>
+            <el-button type="warning" v-if="scope.row.state === 2" size="mini" @click="handleRevoke(scope.row)">撤销</el-button>
+            <el-button type="danger" v-if="scope.row.state === 1" size="mini" @click="handleDelete(scope.row)">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -114,16 +126,26 @@
       </div>
     </div>
 
+    <!-- 详情弹窗 -->
     <DetailModal ref="detailModalRef"></DetailModal>
+
+    <!-- 创建弹窗 -->
+    <ActivityModal @refresh="getAll()" ref="activityModalRef"></ActivityModal>
+
+    <!-- 预览视频 -->
+    <PreviewVideo ref="previewVideoRef"></PreviewVideo>
   </div>
 </template>
 
 <script>
-import { getActivitiesAllApi } from '@/api/rm-bank'
+import { getActivitiesAllApi, deleteActivityApi, patchPeopleBankActivitiesByIdApi } from '@/api/rm-bank'
 import DetailModal from './components/DetailModal.vue'
+import ActivityModal from './components/ActivityModal.vue'
+import PreviewVideo from './components/PreviewVideo.vue'
+import { getTypeEnumByValue, ACTIVITY_STATUS_INFO } from './components/utils'
 
 export default {
-  components: {DetailModal},
+  components: { DetailModal, ActivityModal, PreviewVideo },
   data() {
     return {
       query: {
@@ -131,10 +153,12 @@ export default {
         pageSize: 10,
         search: '',
         typeEnum: '',
-        opportunityType: ''
+        opportunityType: '',
+        stateEnum: ''
       },
       total: 1,
-      tableData: []
+      tableData: [],
+      ACTIVITY_STATUS_INFO
     }
   },
   created() {
@@ -167,6 +191,47 @@ export default {
         areaCode: []
       }
       this.getAll()
+    },
+
+    // 删除活动
+    handleDelete(activityInfo) {
+      this.$confirm(`是否删除【${activityInfo.title}】这项吗?`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(async () => {
+          const res = await deleteActivityApi({ 'activities.id': activityInfo.id })
+          if (res.code == '200') {
+            this.$message.success('删除成功')
+            this.getAll()
+          }
+        })
+        .catch(() => {})
+    },
+
+    // 撤销活动
+    handleRevoke(row) {
+      this.$confirm(`是否要撤销【${row.title}】这项吗?`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(async () => {
+          const revokeData = {
+            activities: {
+              ...row,
+              typeEnum: getTypeEnumByValue(Number(row.type)),
+              stateEnum: 'CANCELLED',
+              type: undefined,
+              state: undefined
+            }
+          }
+          await patchPeopleBankActivitiesByIdApi(revokeData)
+          this.$message.success('撤销成功')
+          this.getAll()
+        })
+        .catch(() => {})
     }
   }
 }

+ 198 - 0
src/views/rm-bank/member/components/EditMember.vue

@@ -0,0 +1,198 @@
+<template>
+  <el-dialog :close-on-click-modal="false" title="编辑用户信息" :visible.sync="editMemberVisible" width="50%">
+    <el-form :rules="editMemberRules" ref="editMemberRef" :model="editMemberForm" label-width="auto">
+      <el-form-item label="昵称" prop="nickName">
+        <el-input v-model="editMemberForm.nickName" placeholder="请输会员昵称"></el-input>
+      </el-form-item>
+      <el-form-item label="会员头像" prop="avatar">
+        <ImageUpload :limit="1" v-model="editMemberForm.avatar"></ImageUpload>
+      </el-form-item>
+
+      <el-form-item label="会员电话" prop="mobile">
+        <el-input v-model="editMemberForm.mobile" placeholder="请填写会员手机号"></el-input>
+      </el-form-item>
+
+      <el-form-item label="会员简介" prop="profile">
+        <el-input type="textarea" v-model="editMemberForm.profile" placeholder="请填写会员简介"></el-input>
+      </el-form-item>
+
+      <el-form-item label="公司名称" prop="companyName">
+        <el-input v-model="editMemberForm.companyName" placeholder="请填写会员公司名称"></el-input>
+      </el-form-item>
+
+      <el-form-item label="职位" prop="positions">
+        <el-input v-model="editMemberForm.positions" placeholder="请填写会员职位"></el-input>
+      </el-form-item>
+
+      <el-form-item label="公司地址" prop="companyAddress">
+        <el-cascader v-show="isEditAdddress" v-model="editMemberForm.companyAddress" style="width: 100%" :props="areaData" ref="cascaderRef" @change="handleCascaderChange"></el-cascader>
+        <div :style="{ display: isEditAdddress ? 'none' : 'flex' }">
+          <el-input readonly :value="editMemberForm.companyAddress"></el-input>
+          <el-button style="margin-left: 20px" type="primary" size="mini" @click="isEditAdddress = true">修改区域</el-button>
+        </div>
+      </el-form-item>
+
+      <el-form-item label="公司详细地址" prop="companyAddressDetail">
+        <el-input type="textarea" v-model="editMemberForm.companyAddressDetail" placeholder="请填写公司详细地址"></el-input>
+      </el-form-item>
+
+      <el-form-item label="公司简介" prop="companyProfile">
+        <el-input type="textarea" v-model="editMemberForm.companyProfile" placeholder="请填写公司简介"></el-input>
+      </el-form-item>
+
+      <el-form-item label="名片风格" prop="businessCardStyle">
+        <el-select style="width: 100%" v-model="editMemberForm.businessCardStyle" placeholder="请选择名片风格">
+          <el-option label="幽远湛蓝" :value="1"></el-option>
+          <el-option label="活力亮橙" :value="2"></el-option>
+          <el-option label="光辉灿黄" :value="3"></el-option>
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button type="info" @click="handleClose">取消</el-button>
+        <el-button type="primary" :loading="isLoading" @click="handleConfirm">确认修改</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+import ImageUpload from '@/components/ImageUpload'
+import { getProvinceList, getChildAreaList } from '@/api/address'
+import { editMemberApi } from '@/api/rm-bank'
+
+export default {
+  components: {
+    ImageUpload
+  },
+  data() {
+    return {
+      isLoading: false,
+      editMemberVisible: false,
+      editMemberForm: {
+        id: undefined,
+        nickName: '',
+        avatar: '',
+        mobile: '',
+        profile: '',
+        companyName: '',
+        positions: '',
+        companyAddress: '',
+        companyAddressDetail: '',
+        companyProfile: '',
+        businessCardStyle: 1
+      },
+      editMemberRules: {
+        nickName: [{ required: true, message: '请输入会员昵称', required: true }],
+        mobile: [
+          { required: true, message: '请输入电话号码', trigger: 'blur' },
+          { pattern: /^1[3|4|5|7|8][0-9]\d{8}$/, message: '电话号码必须是11位数字', trigger: 'blur' }
+        ]
+      },
+      selectAddressLabel: '',
+      isEditAdddress: false,
+      areaData: {
+        lazy: true,
+        label: 'name',
+        value: 'id',
+        lazyLoad(node, resolve) {
+          const { level, value } = node
+          if (level === 0) {
+            getProvinceList().then((res) => {
+              resolve(res.data)
+            })
+          } else if (level != 0) {
+            getChildAreaList(value).then((res) => {
+              resolve(
+                res.data.map((item) => {
+                  item.leaf = level === 3
+                  return item
+                })
+              )
+            })
+          }
+        }
+      }
+    }
+  },
+
+  watch: {
+    editMemberVisible(visible) {
+      if (!visible) {
+        this.editMemberForm = {
+          id: undefined,
+          nickName: '',
+          avatar: '',
+          mobile: '',
+          profile: '',
+          companyName: '',
+          positions: '',
+          companyAddress: '',
+          companyAddressDetail: '',
+          companyProfile: '',
+          businessCardStyle: 1
+        }
+        this.isEditAdddress = false
+      }
+    }
+  },
+
+  methods: {
+    show(memberInfo) {
+      if (!memberInfo) {
+        return this.$message.error('数据错误')
+      }
+
+      this.editMemberForm.id = memberInfo.id
+      this.editMemberForm.nickName = memberInfo.nickName
+      this.editMemberForm.avatar = memberInfo.avatar
+      this.editMemberForm.mobile = memberInfo.mobile
+      this.editMemberForm.profile = memberInfo.profile
+      this.editMemberForm.companyName = memberInfo.companyName
+      this.editMemberForm.positions = memberInfo.positions
+      this.editMemberForm.companyAddress = memberInfo.companyAddress.split(' ')[0]
+      this.editMemberForm.companyAddressDetail = memberInfo.companyAddress.split(' ')[1] || undefined
+      this.editMemberForm.companyProfile = memberInfo.companyProfile
+      this.editMemberForm.businessCardStyle = memberInfo.businessCardStyle
+
+      this.editMemberVisible = true
+    },
+    handleClose() {
+      this.editMemberVisible = false
+    },
+    async handleConfirm() {
+      const submitData = JSON.parse(JSON.stringify(this.editMemberForm))
+      submitData.companyAddress = this.selectAddressLabel
+      if (submitData.companyAddressDetail) {
+        submitData.companyAddress = submitData.companyAddress + ' ' + this.editMemberForm.companyAddressDetail
+      }
+
+      try {
+        this.isLoading = true
+        await this.$refs.editMemberRef.validate()
+        await editMemberApi({
+          member: {
+            ...submitData
+          }
+        })
+        this.$message.success('编辑成功')
+        this.$emit('refresh')
+        this.handleClose()
+      } catch (error) {
+      } finally {
+        this.isLoading = false
+      }
+    },
+    handleCascaderChange() {
+      const checkedNode = this.$refs.cascaderRef.getCheckedNodes()
+      const { pathLabels } = checkedNode[0]
+      this.selectAddressLabel = pathLabels.join('')
+
+      console.log(checkedNode)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 97 - 0
src/views/rm-bank/member/components/EditMemberINClub.vue

@@ -0,0 +1,97 @@
+<template>
+  <el-dialog append-to-body :close-on-click-modal="false" title="编辑用户在俱乐部的信息" :visible.sync="editMemberInClubVisible" width="50%">
+    <div class="main-area">
+      <el-form ref="formRef" :model="editMemberInClubForm" label-width="auto">
+        <el-form-item label="会员身份" prop="identity">
+          <el-select style="width: 100%" v-model="editMemberInClubForm.clubMember.identity">
+            <el-option v-for="item in iList" :key="item.value" :label="item.label" :value="item.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="信用积分" prop="creditScore">
+          <el-input type="number" v-model.number="editMemberInClubForm.clubMember.creditScore" placeholder="请填写会员积分"></el-input>
+        </el-form-item>
+        <el-form-item label="邀请人" prop="inviter">
+          <el-input v-model="editMemberInClubForm.clubMember.inviter" placeholder="请填写邀请人"></el-input>
+        </el-form-item>
+        <el-form-item label="是否是管理员" prop="isAdmin">
+          <el-radio-group v-model="editMemberInClubForm.clubMember.isAdmin">
+            <el-radio-button :label="1">是</el-radio-button>
+            <el-radio-button :label="0">否</el-radio-button>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+    </div>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button type="info" @click="handleClose">取消</el-button>
+        <el-button type="primary" :loading="isLoading" @click="handleEdit">确认修改</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+import { getClubIdentityListApi, patchClubMemberApi } from '@/api/rm-bank'
+
+export default {
+  data() {
+    return {
+      editMemberInClubVisible: false,
+      isLoading: false,
+      editMemberInClubForm: {
+        club: { id: undefined },
+        clubMember: { memberId: undefined, state: undefined, identity: undefined, creditScore: undefined, isAdmin: undefined, inviter: undefined }
+      },
+      iList: [] // 身份列表
+    }
+  },
+
+  mounted() {
+    this.getClubIdentityList()
+  },
+
+  methods: {
+    show(data) {
+      this.resetDialog()
+      Object.assign(this.editMemberInClubForm, data)
+      this.editMemberInClubVisible = true
+    },
+
+    async getClubIdentityList() {
+      const res = await getClubIdentityListApi()
+      if (res.code === '200') {
+        this.iList = res.data.map((item) => ({ label: item, value: item }))
+      } else {
+        this.iList = []
+      }
+    },
+
+    // 编辑会员信息
+    async handleEdit() {
+      try {
+        this.isLoading = true
+        await patchClubMemberApi(this.editMemberInClubForm)
+        this.$message.success('编辑成功')
+        this.handleClose()
+        this.$emit('refresh')
+      } finally {
+        this.isLoading = false
+      }
+    },
+
+    handleClose() {
+      this.editMemberInClubVisible = false
+    },
+
+    resetDialog() {
+      this.editMemberInClubForm = {
+        club: { id: undefined },
+        clubMember: { memberId: undefined, state: undefined, identity: undefined, creditScore: undefined, isAdmin: undefined, inviter: undefined }
+      }
+      this.isLoading = false
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 177 - 0
src/views/rm-bank/member/components/MenberDetail.vue

@@ -0,0 +1,177 @@
+<template>
+  <div>
+    <el-dialog :close-on-click-modal="false" title="编辑用户信息" :visible.sync="memberDetailVisible" width="50%">
+      <div v-loading.lock="isLoading">
+        <!-- 会员信息 -->
+        <el-descriptions title="会员信息" :column="2" border>
+          <el-descriptions-item label="会员昵称">{{ member.nickName || '--' }}</el-descriptions-item>
+          <el-descriptions-item label="联系电话">{{ member.mobile || '--' }}</el-descriptions-item>
+          <el-descriptions-item label="会员头像">
+            <el-image style="width: 40px; height: 40px; border-radius: 50%" v-if="member.avatar" :src="member.avatar" :preview-src-list="[member.avatar]" fit="cover" />
+            <span v-else>--</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="团蜂ID">{{ member.buyerUserId || '--' }}</el-descriptions-item>
+          <el-descriptions-item label="个人简介" :span="2">{{ member.profile || '--' }}</el-descriptions-item>
+          <el-descriptions-item label="公司名称">{{ member.companyName || '--' }}</el-descriptions-item>
+          <el-descriptions-item label="职位">{{ member.positions || '--' }}</el-descriptions-item>
+
+          <el-descriptions-item label="公司地址" :span="2">{{ member.companyAddress || '--' }}</el-descriptions-item>
+          <el-descriptions-item label="公司简介" :span="2">{{ member.companyProfile || '--' }}</el-descriptions-item>
+          <el-descriptions-item label="名片风格" :span="2">
+            <el-tag v-if="member.businessCardStyle === 1" type="primary">蓝色</el-tag>
+            <el-tag v-if="member.businessCardStyle === 2" type="warning">橙色</el-tag>
+            <el-tag v-if="member.businessCardStyle === 3" type="danger">黄色</el-tag>
+          </el-descriptions-item>
+        </el-descriptions>
+
+        <!-- 俱乐部信息 -->
+        <h3 style="font-size: 16px; font-weight: 700; color: #303133; margin: 20px 0">加入俱乐部列表</h3>
+        <el-table border :data="clubs" :style="{ width: '100%' }">
+          <el-table-column align="center" type="index" label="#" width="55"></el-table-column>
+          <el-table-column align="center" prop="clubId" label="俱乐部ID" width="80" />
+          <el-table-column align="center" prop="clubName" label="俱乐部名称" width="180" />
+          <el-table-column align="center" prop="identity" label="所属身份" width="100" />
+          <el-table-column align="center" prop="state" label="审核状态" width="100">
+            <template slot-scope="scope">
+              <el-tag type="warning" size="mini" v-if="scope.row.state === 1">已申请</el-tag>
+              <el-tag type="success" size="mini" v-if="scope.row.state === 2">已通过</el-tag>
+              <el-tag type="danger" size="mini" v-if="scope.row.state === 3">已拒绝</el-tag>
+              <el-tag type="danger" size="mini" v-if="scope.row.state === 4">已冻结</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column width="200" align="center" prop="creditScore" label="信用积分"></el-table-column>
+          <el-table-column align="center" prop="isAdmin" label="是否是管理员" width="180">
+            <template slot-scope="scope">
+              <el-tag type="success" size="mini" v-if="scope.row.isAdmin === 1">是</el-tag>
+              <el-tag type="info" size="mini" v-if="scope.row.isAdmin === 0">否</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column align="center" prop="inviter" label="邀请人" width="100">
+            <template slot-scope="scope">
+              {{ scope.row.inviter || '--' }}
+            </template>
+          </el-table-column>
+          <el-table-column width="180" align="center" prop="createTime" label="加入时间"></el-table-column>
+
+          <el-table-column width="180" label="操作" align="center" show-overflow-tooltip fixed="right">
+            <template slot-scope="scope">
+              <div class="btnList">
+                <el-popover placement="top-start" :title="' '" width="150" trigger="click">
+                  <div style="font-size: 14px; color: #303133">
+                    请对【{{ member.nickName }}】的申请进行审核
+                    <div style="display: flex; justify-content: space-between; margin-top: 8px">
+                      <el-button type="danger" size="mini" @click="handleApply(3, scope.row)">拒绝</el-button>
+                      <el-button type="success" size="mini" @click="handleApply(2, scope.row)">通过</el-button>
+                    </div>
+                  </div>
+                  <el-button slot="reference" size="mini" type="primary" :disabled="scope.row.state !== 1">审核</el-button>
+                </el-popover>
+                <el-button style="margin-left: 8px" size="mini" type="success" :disabled="scope.row.state !== 2" @click="handleEditMemberInfo(scope.row)">修改会员信息</el-button>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button type="info" @click="handleClose">取消</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+    <!-- 编辑会员在对应俱乐部的信息 -->
+    <EditMemberINClub @refresh="getMemberInfo" ref="editMemberINClubRef"></EditMemberINClub>
+  </div>
+</template>
+
+<script>
+import { Loading } from 'element-ui'
+import { getMemberByMemberIdApi, patchClubMemberApi } from '@/api/rm-bank'
+import EditMemberINClub from './EditMemberINClub.vue'
+
+const getEditMemberClubInfoData = (row, memberId) => ({
+  club: { id: row.clubId },
+  clubMember: { memberId, state: row.state, identity: row.identity, creditScore: row.creditScore, isAdmin: row.isAdmin, inviter: row.inviter }
+})
+
+export default {
+  components: { EditMemberINClub },
+  data() {
+    return {
+      memberDetailVisible: false,
+      isLoading: false,
+      clubs: [], // 会员所在俱乐部列表
+      member: {}, // 会员基本信息
+      memberId: undefined
+    }
+  },
+
+  methods: {
+    show(memberId) {
+      this.resetDialog()
+      if (!memberId) {
+        return this.$message.error('数据错误')
+      }
+      this.memberId = memberId
+      this.getMemberInfo()
+      this.memberDetailVisible = true
+    },
+
+    // 编辑信息
+    handleEditMemberInfo(row) {
+      this.$refs.editMemberINClubRef.show(getEditMemberClubInfoData(row, this.memberId))
+    },
+
+    // 获取详情
+    async getMemberInfo() {
+      try {
+        this.isLoading = true
+        const res = await getMemberByMemberIdApi({ 'member.id': this.memberId })
+        if (res.code === '200') {
+          const { clubs, member } = res.data
+          this.clubs = clubs || []
+          this.member = member || {}
+        }
+        this.isLoading = false
+      } catch (error) {
+        this.$message.error('会员详情获取失败')
+        this.isLoading = false
+      }
+    },
+
+    // 关闭
+    handleClose() {
+      this.memberDetailVisible = false
+    },
+
+    // 重置相关数据
+    resetDialog() {
+      this.isLoading = false
+      this.clubs = []
+      this.member = {}
+      this.memberId = undefined
+    },
+
+    // 审核
+    async handleApply(state, info) {
+      const instance = Loading.service()
+      try {
+        const applyData = getEditMemberClubInfoData(info, this.memberId)
+        applyData.clubMember.state = state
+        await patchClubMemberApi(applyData)
+        document.body.click()
+        this.$message.success('审核成功')
+        this.getMemberInfo()
+      } finally {
+        instance.close()
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep .el-descriptions-item__cell {
+  width: 30px;
+}
+</style>

+ 14 - 4
src/views/rm-bank/member/index.vue

@@ -32,7 +32,7 @@
         <el-table-column prop="positions" label="职位" align="center" width="220" />
         <el-table-column show-overflow-tooltip prop="companyAddress" label="公司地址" align="center" width="220" />
         <el-table-column show-overflow-tooltip prop="companyProfile" label="公司简介" align="center" width="220" />
-        <el-table-column prop="businessCardStyle" label="名片风格" align="center" width="220">
+        <el-table-column prop="businessCardStyle" label="名片风格" align="center" width="120">
           <template slot-scope="scope">
             <el-tag type="primary" size="mini" v-if="scope.row.businessCardStyle === 1">蓝色</el-tag>
             <el-tag type="warning" size="mini" v-if="scope.row.businessCardStyle === 2">橙色</el-tag>
@@ -40,13 +40,14 @@
           </template>
         </el-table-column>
 
-        <!-- <el-table-column width="280" label="操作" align="center" show-overflow-tooltip>
+        <el-table-column width="100" label="操作" align="center" show-overflow-tooltip fixed="right">
           <template slot-scope="scope">
             <div class="btnList">
-               <el-button type="text" @click="$refs.clubModalRef && $refs.clubModalRef.show(scope.row)">编辑</el-button> 
+              <el-button type="text" @click="$refs.menberDetailRef && $refs.menberDetailRef.show(scope.row.id)">详情</el-button>
+              <el-button type="text" @click="$refs.editMemberRef && $refs.editMemberRef.show(scope.row)">编辑</el-button>
             </div>
           </template>
-        </el-table-column> -->
+        </el-table-column>
       </el-table>
       <div class="fenye">
         <el-pagination
@@ -60,13 +61,22 @@
         />
       </div>
     </div>
+
+    <!-- 编辑用户信息 -->
+    <EditMember @refresh="getAll" ref="editMemberRef"></EditMember>
+
+    <!-- 查看详情 -->
+    <MenberDetail ref="menberDetailRef"></MenberDetail>
   </div>
 </template>
 
 <script>
 import { getClubMemberApi } from '@/api/rm-bank'
+import EditMember from './components/EditMember.vue'
+import MenberDetail from './components/MenberDetail.vue'
 
 export default {
+  components: { EditMember, MenberDetail },
   data() {
     return {
       query: { page: 1, pageSize: 10, search: '' },