jhy 7 ヶ月 前
コミット
c37d35eeab

+ 54 - 0
src/api/member/trade/index.ts

@@ -0,0 +1,54 @@
+import request from '@/config/axios'
+
+// 交易记录 VO
+export interface TradeVO {
+  id: number // 编号
+  rechargeId: number // 充值用户id
+  rechargeNickname: string // 充值用户昵称
+  rechargeComboId: number // 充值套餐id
+  rechargeComboName: string // 充值套餐名称
+  rechargeLevelId: number // 充值用户等级id
+  rechargeLevelName: string // 充值用户等级
+  rechargeMoney: number // 用户充值金额
+  shopGiveMoney: number // 店家赠送金额
+  centsId: number // 分润用户ID
+  centsNickname: string // 分润用户昵称
+  centsLevelId: number // 分润用户等级id
+  centsLevelName: string // 分润用户等级
+  centsLostMoney: number // 分润金额
+  platformRate: number // 平台费率
+  platformMoney: number // 平台金额
+}
+
+// 交易记录 API
+export const TradeApi = {
+  // 查询交易记录分页
+  getTradePage: async (params: any) => {
+    return await request.get({ url: `/member/trade/page`, params })
+  },
+
+  // 查询交易记录详情
+  getTrade: async (id: number) => {
+    return await request.get({ url: `/member/trade/get?id=` + id })
+  },
+
+  // 新增交易记录
+  createTrade: async (data: TradeVO) => {
+    return await request.post({ url: `/member/trade/create`, data })
+  },
+
+  // 修改交易记录
+  updateTrade: async (data: TradeVO) => {
+    return await request.put({ url: `/member/trade/update`, data })
+  },
+
+  // 删除交易记录
+  deleteTrade: async (id: number) => {
+    return await request.delete({ url: `/member/trade/delete?id=` + id })
+  },
+
+  // 导出交易记录 Excel
+  exportTrade: async (params) => {
+    return await request.download({ url: `/member/trade/export-excel`, params })
+  }
+}

+ 44 - 0
src/api/member/tree/index.ts

@@ -0,0 +1,44 @@
+import request from '@/config/axios'
+
+// 会员树根 VO
+export interface TreeVO {
+  id: number // 主键
+  rootId: number // 树根id
+  appendParentId: number // 新用户父节点
+  treeCurrentDepth: number // 树深度
+  layerLostCount: number // 本层剩余座席树
+  treeAllCount: number // 树已入驻座席数
+}
+
+// 会员树根 API
+export const TreeApi = {
+  // 查询会员树根分页
+  getTreePage: async (params: any) => {
+    return await request.get({ url: `/member/tree/page`, params })
+  },
+
+  // 查询会员树根详情
+  getTree: async (id: number) => {
+    return await request.get({ url: `/member/tree/get?id=` + id })
+  },
+
+  // 新增会员树根
+  createTree: async (data: TreeVO) => {
+    return await request.post({ url: `/member/tree/create`, data })
+  },
+
+  // 修改会员树根
+  updateTree: async (data: TreeVO) => {
+    return await request.put({ url: `/member/tree/update`, data })
+  },
+
+  // 删除会员树根
+  deleteTree: async (id: number) => {
+    return await request.delete({ url: `/member/tree/delete?id=` + id })
+  },
+
+  // 导出会员树根 Excel
+  exportTree: async (params) => {
+    return await request.download({ url: `/member/tree/export-excel`, params })
+  }
+}

+ 11 - 0
src/api/member/user/index.ts

@@ -46,3 +46,14 @@ export const updateUserLevel = async (data: any) => {
 export const updateUserPoint = async (data: any) => {
   return await request.put({ url: `/member/user/update-point`, data })
 }
+
+// 新增用户
+export const createUser = async (data: UserVO) => {
+  return await request.put({ url: `/member/user/create`, data })
+}
+
+// 获取用户套餐
+export const getComboList = async () => {
+  return await request.get({ url: `/member/combo/list` })
+}
+

+ 1 - 1
src/views/member/tag/components/MemberTagSelect.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-select v-model="tagIds" placeholder="请选择用户标签" clearable multiple class="!w-240px">
+  <el-select v-model="tagIds" placeholder="请选择用户标签" clearable multiple>
     <el-option v-for="tag in tags" :key="tag.id" :label="tag.name" :value="tag.id" />
   </el-select>
   <el-button

+ 173 - 0
src/views/member/trade/TradeForm.vue

@@ -0,0 +1,173 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="充值用户id" prop="rechargeId">
+        <el-input v-model="formData.rechargeId" placeholder="请输入充值用户id" />
+      </el-form-item>
+      <el-form-item label="充值用户昵称" prop="rechargeNickname">
+        <el-input v-model="formData.rechargeNickname" placeholder="请输入充值用户昵称" />
+      </el-form-item>
+      <el-form-item label="充值套餐id" prop="rechargeComboId">
+        <el-input v-model="formData.rechargeComboId" placeholder="请输入充值套餐id" />
+      </el-form-item>
+      <el-form-item label="充值套餐名称" prop="rechargeComboName">
+        <el-input v-model="formData.rechargeComboName" placeholder="请输入充值套餐名称" />
+      </el-form-item>
+      <el-form-item label="充值用户等级id" prop="rechargeLevelId">
+        <el-input v-model="formData.rechargeLevelId" placeholder="请输入充值用户等级id" />
+      </el-form-item>
+      <el-form-item label="充值用户等级" prop="rechargeLevelName">
+        <el-input v-model="formData.rechargeLevelName" placeholder="请输入充值用户等级" />
+      </el-form-item>
+      <el-form-item label="用户充值金额" prop="rechargeMoney">
+        <el-input v-model="formData.rechargeMoney" placeholder="请输入用户充值金额" />
+      </el-form-item>
+      <el-form-item label="店家赠送金额" prop="shopGiveMoney">
+        <el-input v-model="formData.shopGiveMoney" placeholder="请输入店家赠送金额" />
+      </el-form-item>
+      <el-form-item label="分润用户ID" prop="centsId">
+        <el-input v-model="formData.centsId" placeholder="请输入分润用户ID" />
+      </el-form-item>
+      <el-form-item label="分润用户昵称" prop="centsNickname">
+        <el-input v-model="formData.centsNickname" placeholder="请输入分润用户昵称" />
+      </el-form-item>
+      <el-form-item label="分润用户等级id" prop="centsLevelId">
+        <el-input v-model="formData.centsLevelId" placeholder="请输入分润用户等级id" />
+      </el-form-item>
+      <el-form-item label="分润用户等级" prop="centsLevelName">
+        <el-input v-model="formData.centsLevelName" placeholder="请输入分润用户等级" />
+      </el-form-item>
+      <el-form-item label="分润金额" prop="centsLostMoney">
+        <el-input v-model="formData.centsLostMoney" placeholder="请输入分润金额" />
+      </el-form-item>
+      <el-form-item label="平台费率" prop="platformRate">
+        <el-input v-model="formData.platformRate" placeholder="请输入平台费率" />
+      </el-form-item>
+      <el-form-item label="平台金额" prop="platformMoney">
+        <el-input v-model="formData.platformMoney" placeholder="请输入平台金额" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { TradeApi, TradeVO } from '@/api/member/trade'
+
+/** 交易记录 表单 */
+defineOptions({ name: 'TradeForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  rechargeId: undefined,
+  rechargeNickname: undefined,
+  rechargeComboId: undefined,
+  rechargeComboName: undefined,
+  rechargeLevelId: undefined,
+  rechargeLevelName: undefined,
+  rechargeMoney: undefined,
+  shopGiveMoney: undefined,
+  centsId: undefined,
+  centsNickname: undefined,
+  centsLevelId: undefined,
+  centsLevelName: undefined,
+  centsLostMoney: undefined,
+  platformRate: undefined,
+  platformMoney: undefined
+})
+const formRules = reactive({
+  rechargeNickname: [{ required: true, message: '充值用户昵称不能为空', trigger: 'blur' }],
+  rechargeComboId: [{ required: true, message: '充值套餐id不能为空', trigger: 'blur' }],
+  rechargeComboName: [{ required: true, message: '充值套餐名称不能为空', trigger: 'blur' }],
+  rechargeLevelId: [{ required: true, message: '充值用户等级id不能为空', trigger: 'blur' }],
+  rechargeMoney: [{ required: true, message: '用户充值金额不能为空', trigger: 'blur' }],
+  shopGiveMoney: [{ required: true, message: '店家赠送金额不能为空', trigger: 'blur' }],
+  centsId: [{ required: true, message: '分润用户ID不能为空', trigger: 'blur' }],
+  centsLevelId: [{ required: true, message: '分润用户等级id不能为空', trigger: 'blur' }],
+  centsLevelName: [{ required: true, message: '分润用户等级不能为空', trigger: 'blur' }],
+  centsLostMoney: [{ required: true, message: '分润金额不能为空', trigger: 'blur' }],
+  platformRate: [{ required: true, message: '平台费率不能为空', trigger: 'blur' }],
+  platformMoney: [{ required: true, message: '平台金额不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await TradeApi.getTrade(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as TradeVO
+    if (formType.value === 'create') {
+      await TradeApi.createTrade(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await TradeApi.updateTrade(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    rechargeId: undefined,
+    rechargeNickname: undefined,
+    rechargeComboId: undefined,
+    rechargeComboName: undefined,
+    rechargeLevelId: undefined,
+    rechargeLevelName: undefined,
+    rechargeMoney: undefined,
+    shopGiveMoney: undefined,
+    centsId: undefined,
+    centsNickname: undefined,
+    centsLevelId: undefined,
+    centsLevelName: undefined,
+    centsLostMoney: undefined,
+    platformRate: undefined,
+    platformMoney: undefined
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 341 - 0
src/views/member/trade/index.vue

@@ -0,0 +1,341 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="充值用户id" prop="rechargeId">
+        <el-input
+          v-model="queryParams.rechargeId"
+          placeholder="请输入充值用户id"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="充值用户昵称" prop="rechargeNickname">
+        <el-input
+          v-model="queryParams.rechargeNickname"
+          placeholder="请输入充值用户昵称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="充值套餐id" prop="rechargeComboId">
+        <el-input
+          v-model="queryParams.rechargeComboId"
+          placeholder="请输入充值套餐id"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="充值套餐名称" prop="rechargeComboName">
+        <el-input
+          v-model="queryParams.rechargeComboName"
+          placeholder="请输入充值套餐名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="充值用户等级id" prop="rechargeLevelId">
+        <el-input
+          v-model="queryParams.rechargeLevelId"
+          placeholder="请输入充值用户等级id"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="充值用户等级" prop="rechargeLevelName">
+        <el-input
+          v-model="queryParams.rechargeLevelName"
+          placeholder="请输入充值用户等级"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="用户充值金额" prop="rechargeMoney">
+        <el-input
+          v-model="queryParams.rechargeMoney"
+          placeholder="请输入用户充值金额"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="店家赠送金额" prop="shopGiveMoney">
+        <el-input
+          v-model="queryParams.shopGiveMoney"
+          placeholder="请输入店家赠送金额"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="分润用户ID" prop="centsId">
+        <el-input
+          v-model="queryParams.centsId"
+          placeholder="请输入分润用户ID"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="分润用户昵称" prop="centsNickname">
+        <el-input
+          v-model="queryParams.centsNickname"
+          placeholder="请输入分润用户昵称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="分润用户等级id" prop="centsLevelId">
+        <el-input
+          v-model="queryParams.centsLevelId"
+          placeholder="请输入分润用户等级id"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="分润用户等级" prop="centsLevelName">
+        <el-input
+          v-model="queryParams.centsLevelName"
+          placeholder="请输入分润用户等级"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="分润金额" prop="centsLostMoney">
+        <el-input
+          v-model="queryParams.centsLostMoney"
+          placeholder="请输入分润金额"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="平台费率" prop="platformRate">
+        <el-input
+          v-model="queryParams.platformRate"
+          placeholder="请输入平台费率"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="平台金额" prop="platformMoney">
+        <el-input
+          v-model="queryParams.platformMoney"
+          placeholder="请输入平台金额"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-220px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['member:trade:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['member:trade:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="编号" align="center" prop="id" />
+      <el-table-column label="充值用户id" align="center" prop="rechargeId" />
+      <el-table-column label="充值用户昵称" align="center" prop="rechargeNickname" />
+      <el-table-column label="充值套餐id" align="center" prop="rechargeComboId" />
+      <el-table-column label="充值套餐名称" align="center" prop="rechargeComboName" />
+      <el-table-column label="充值用户等级id" align="center" prop="rechargeLevelId" />
+      <el-table-column label="充值用户等级" align="center" prop="rechargeLevelName" />
+      <el-table-column label="用户充值金额" align="center" prop="rechargeMoney" />
+      <el-table-column label="店家赠送金额" align="center" prop="shopGiveMoney" />
+      <el-table-column label="分润用户ID" align="center" prop="centsId" />
+      <el-table-column label="分润用户昵称" align="center" prop="centsNickname" />
+      <el-table-column label="分润用户等级id" align="center" prop="centsLevelId" />
+      <el-table-column label="分润用户等级" align="center" prop="centsLevelName" />
+      <el-table-column label="分润金额" align="center" prop="centsLostMoney" />
+      <el-table-column label="平台费率" align="center" prop="platformRate" />
+      <el-table-column label="平台金额" align="center" prop="platformMoney" />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['member:trade:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['member:trade:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <TradeForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { TradeApi, TradeVO } from '@/api/member/trade'
+import TradeForm from './TradeForm.vue'
+
+/** 交易记录 列表 */
+defineOptions({ name: 'MemberTrade' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<TradeVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  rechargeId: undefined,
+  rechargeNickname: undefined,
+  rechargeComboId: undefined,
+  rechargeComboName: undefined,
+  rechargeLevelId: undefined,
+  rechargeLevelName: undefined,
+  rechargeMoney: undefined,
+  shopGiveMoney: undefined,
+  centsId: undefined,
+  centsNickname: undefined,
+  centsLevelId: undefined,
+  centsLevelName: undefined,
+  centsLostMoney: undefined,
+  platformRate: undefined,
+  platformMoney: undefined,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await TradeApi.getTradePage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await TradeApi.deleteTrade(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await TradeApi.exportTrade(queryParams)
+    download.excel(data, '交易记录.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

+ 115 - 0
src/views/member/tree/TreeForm.vue

@@ -0,0 +1,115 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="树根id" prop="rootId">
+        <el-input v-model="formData.rootId" placeholder="请输入树根id" />
+      </el-form-item>
+      <el-form-item label="新用户父节点" prop="appendParentId">
+        <el-input v-model="formData.appendParentId" placeholder="请输入新用户父节点" />
+      </el-form-item>
+      <el-form-item label="树深度" prop="treeCurrentDepth">
+        <el-input v-model="formData.treeCurrentDepth" placeholder="请输入树深度" />
+      </el-form-item>
+      <el-form-item label="本层剩余座席树" prop="layerLostCount">
+        <el-input v-model="formData.layerLostCount" placeholder="请输入本层剩余座席树" />
+      </el-form-item>
+      <el-form-item label="树已入驻座席数" prop="treeAllCount">
+        <el-input v-model="formData.treeAllCount" placeholder="请输入树已入驻座席数" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { TreeApi, TreeVO } from '@/api/member/tree'
+
+/** 会员树根 表单 */
+defineOptions({ name: 'TreeForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  rootId: undefined,
+  appendParentId: undefined,
+  treeCurrentDepth: undefined,
+  layerLostCount: undefined,
+  treeAllCount: undefined
+})
+const formRules = reactive({
+  rootId: [{ required: true, message: '树根id不能为空', trigger: 'blur' }],
+  appendParentId: [{ required: true, message: '新用户父节点不能为空', trigger: 'blur' }],
+  treeCurrentDepth: [{ required: true, message: '树深度不能为空', trigger: 'blur' }],
+  treeAllCount: [{ required: true, message: '树已入驻座席数不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await TreeApi.getTree(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as TreeVO
+    if (formType.value === 'create') {
+      await TreeApi.createTree(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await TreeApi.updateTree(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    rootId: undefined,
+    appendParentId: undefined,
+    treeCurrentDepth: undefined,
+    layerLostCount: undefined,
+    treeAllCount: undefined
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 211 - 0
src/views/member/tree/index.vue

@@ -0,0 +1,211 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="树根id" prop="rootId">
+        <el-input
+          v-model="queryParams.rootId"
+          placeholder="请输入树根id"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="新用户父节点" prop="appendParentId">
+        <el-input
+          v-model="queryParams.appendParentId"
+          placeholder="请输入新用户父节点"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="树深度" prop="treeCurrentDepth">
+        <el-input
+          v-model="queryParams.treeCurrentDepth"
+          placeholder="请输入树深度"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="本层剩余座席树" prop="layerLostCount">
+        <el-input
+          v-model="queryParams.layerLostCount"
+          placeholder="请输入本层剩余座席树"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="树已入驻座席数" prop="treeAllCount">
+        <el-input
+          v-model="queryParams.treeAllCount"
+          placeholder="请输入树已入驻座席数"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['member:tree:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['member:tree:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="主键" align="center" prop="id" />
+      <el-table-column label="树根id" align="center" prop="rootId" />
+      <el-table-column label="新用户父节点" align="center" prop="appendParentId" />
+      <el-table-column label="树深度" align="center" prop="treeCurrentDepth" />
+      <el-table-column label="本层剩余座席树" align="center" prop="layerLostCount" />
+      <el-table-column label="树已入驻座席数" align="center" prop="treeAllCount" />
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['member:tree:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['member:tree:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <TreeForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import download from '@/utils/download'
+import { TreeApi, TreeVO } from '@/api/member/tree'
+import TreeForm from './TreeForm.vue'
+
+/** 会员树根 列表 */
+defineOptions({ name: 'MemberTree' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<TreeVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  rootId: undefined,
+  appendParentId: undefined,
+  treeCurrentDepth: undefined,
+  layerLostCount: undefined,
+  treeAllCount: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await TreeApi.getTreePage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await TreeApi.deleteTree(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await TreeApi.exportTree(queryParams)
+    download.excel(data, '会员树根.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

+ 110 - 27
src/views/member/user/UserForm.vue

@@ -8,9 +8,9 @@
       v-loading="formLoading"
     >
       <el-form-item label="手机号" prop="mobile">
-        <el-input v-model="formData.mobile" placeholder="请输入手机号" />
+        <el-input v-model="formData.mobile" placeholder="请输入手机号"/>
       </el-form-item>
-      <el-form-item label="状态" prop="status">
+      <!-- <el-form-item label="状态" prop="status">
         <el-radio-group v-model="formData.status">
           <el-radio
             v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
@@ -20,15 +20,15 @@
             {{ dict.label }}
           </el-radio>
         </el-radio-group>
-      </el-form-item>
+      </el-form-item> -->
       <el-form-item label="用户昵称" prop="nickname">
-        <el-input v-model="formData.nickname" placeholder="请输入用户昵称" />
+        <el-input v-model="formData.nickname" placeholder="请输入用户昵称"/>
       </el-form-item>
-      <el-form-item label="头像" prop="avatar">
+      <!-- <el-form-item label="头像" prop="avatar">
         <UploadImg v-model="formData.avatar" :limit="1" :is-show-tip="false" />
-      </el-form-item>
+      </el-form-item> -->
       <el-form-item label="真实名字" prop="name">
-        <el-input v-model="formData.name" placeholder="请输入真实名字" />
+        <el-input v-model="formData.name" placeholder="请输入真实名字"/>
       </el-form-item>
       <el-form-item label="用户性别" prop="sex">
         <el-radio-group v-model="formData.sex">
@@ -44,6 +44,7 @@
       <el-form-item label="出生日期" prop="birthday">
         <el-date-picker
           v-model="formData.birthday"
+          style="width:100%;"
           type="date"
           value-format="x"
           placeholder="选择出生日期"
@@ -58,13 +59,28 @@
         />
       </el-form-item>
       <el-form-item label="用户标签" prop="tagIds">
-        <MemberTagSelect v-model="formData.tagIds" show-add />
+        <MemberTagSelect style="width:100%;" v-model="formData.tagIds"/>
       </el-form-item>
-      <el-form-item label="用户分组" prop="groupId">
-        <MemberGroupSelect v-model="formData.groupId" />
+      <el-form-item label="用户套餐" prop="comboId">
+        <el-select
+          v-model="formData.comboId"
+          placeholder="Select"
+          size="large"
+          style="width: 240px"
+        >
+          <el-option
+            v-for="item in comboData"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
       </el-form-item>
+      <!-- <el-form-item label="用户分组" prop="groupId">
+        <MemberGroupSelect v-model="formData.groupId" />
+      </el-form-item> -->
       <el-form-item label="会员备注" prop="mark">
-        <el-input type="textarea" v-model="formData.mark" placeholder="请输入会员备注" />
+        <el-input type="textarea" v-model="formData.mark" placeholder="请输入会员备注"/>
       </el-form-item>
     </el-form>
     <template #footer>
@@ -74,14 +90,14 @@
   </Dialog>
 </template>
 <script setup lang="ts">
-import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import {DICT_TYPE, getIntDictOptions} from '@/utils/dict'
 import * as UserApi from '@/api/member/user'
 import * as AreaApi from '@/api/system/area'
-import { defaultProps } from '@/utils/tree'
+import {defaultProps} from '@/utils/tree'
 import MemberTagSelect from '@/views/member/tag/components/MemberTagSelect.vue'
 import MemberGroupSelect from '@/views/member/group/components/MemberGroupSelect.vue'
 
-const { t } = useI18n() // 国际化
+const {t} = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 
 const dialogVisible = ref(false) // 弹窗的是否展示
@@ -92,30 +108,81 @@ const formData = ref({
   id: undefined,
   mobile: undefined,
   password: undefined,
-  status: undefined,
+  status: 0,
   nickname: undefined,
-  avatar: undefined,
   name: undefined,
   sex: undefined,
   areaId: undefined,
   birthday: undefined,
   mark: undefined,
-  tagIds: [],
-  groupId: undefined
+  comboId: undefined,
+  tagIds: []
 })
 const formRules = reactive({
-  mobile: [{ required: true, message: '手机号不能为空', trigger: 'blur' }],
-  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
+  mobile: [{required: true, message: '手机号不能为空', trigger: 'blur'}],
+  name: [{required: true, message: '真实姓名不能为空', trigger: 'blur'}],
+  nickname: [{required: true, message: '昵称不能为空', trigger: 'blur'}],
+  sex: [{required: true, message: '性别不能为空', trigger: 'blur'}],
+  tagIds: [{required: true, message: '用户标签', trigger: 'blur'}],
+  comboId: [{required: true, message: '用户套餐', trigger: 'blur'}]
 })
 const formRef = ref() // 表单 Ref
 const areaList = ref([]) // 地区列表
+const comboData = ref([])
+
+// 随机生成昵称
+function generateRandomNickname() {
+  const words = ['星空', '梦想', '飞翔', '晨曦', '夜影', '流星', '彩虹', '星辰', '海洋', '火焰'];
+  const adjectives = ['快乐', '自由', '勇敢', '智慧', '温柔', '神秘', '阳光', '梦幻', '浪漫', '宁静'];
+  const randomWord = words[Math.floor(Math.random() * words.length)];
+  const randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)];
+  return `${randomAdjective}${randomWord}`;
+}
+
+// 随机生成姓名
+function generateRandomName() {
+  const firstNames = ['张', '王', '李', '赵', '陈', '杨', '黄', '刘', '朱', '林'];
+  const lastNames = ['伟', '芳', '丽', '强', '华', '明', '涛', '杰', '敏', '娜'];
+  const firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
+  const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
+  return `${firstName}${lastName}`;
+}
+
+// 随机生成手机号
+function generateRandomPhoneNumber() {
+  let result = '1';
+  for (let i = 0; i < 10; i++) {
+    result += Math.floor(Math.random() * 10);
+  }
+  return result;
+}
+
+// 随机生成性别
+function generateRandomGender() {
+  const genders = [1, 2];
+  return genders[Math.floor(Math.random() * genders.length)];
+}
+
+// 随机生成标签
+function generateRandomTag() {
+  const tags = [1,2];
+  return tags[Math.floor(Math.random() * tags.length)];
+}
+
+// 随机生成套餐
+function generateRandomCombo() {
+  const combos = [3, 4, 5];
+  return combos[Math.floor(Math.random() * combos.length)];
+}
+
 
 /** 打开弹窗 */
 const open = async (type: string, id?: number) => {
   dialogVisible.value = true
-  dialogTitle.value = t('action.' + type)
+  dialogTitle.value = id ? '编辑' : '新增'
   formType.value = type
   resetForm()
+  comboData.value = await UserApi.getComboList()
   // 修改时,设置数据
   if (id) {
     formLoading.value = true
@@ -127,8 +194,24 @@ const open = async (type: string, id?: number) => {
   }
   // 获得地区列表
   areaList.value = await AreaApi.getAreaTree()
+
+  formData.value = {
+    id: undefined,
+    mobile: generateRandomPhoneNumber(),
+    password: undefined,
+    status: 0,
+    nickname: generateRandomNickname(),
+    name: generateRandomName(),
+    sex: generateRandomGender(),
+    areaId: undefined,
+    birthday: undefined,
+    mark: 0,
+    comboId: generateRandomCombo(),
+    tagIds: [generateRandomTag()]
+  }
+
 }
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+defineExpose({open}) // 提供 open 方法,用于打开弹窗
 
 /** 提交表单 */
 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
@@ -143,7 +226,7 @@ const submitForm = async () => {
     const data = formData.value as unknown as UserApi.UserVO
     if (formType.value === 'create') {
       // 说明:目前暂时没有新增操作。如果自己业务需要,可以进行扩展
-      // await UserApi.createUser(data)
+      await UserApi.createUser(data)
       message.success(t('common.createSuccess'))
     } else {
       await UserApi.updateUser(data)
@@ -157,22 +240,22 @@ const submitForm = async () => {
   }
 }
 
+
 /** 重置表单 */
 const resetForm = () => {
   formData.value = {
     id: undefined,
     mobile: undefined,
     password: undefined,
-    status: undefined,
+    status: 0,
     nickname: undefined,
-    avatar: undefined,
     name: undefined,
     sex: undefined,
     areaId: undefined,
     birthday: undefined,
     mark: undefined,
-    tagIds: [],
-    groupId: undefined
+    comboId: undefined,
+    tagIds: []
   }
   formRef.value?.resetFields()
 }

+ 24 - 28
src/views/member/user/index.vue

@@ -68,7 +68,10 @@
           <Icon class="mr-5px" icon="ep:refresh" />
           重置
         </el-button>
-        <el-button v-hasPermi="['promotion:coupon:send']" @click="openCoupon">发送优惠券</el-button>
+        <el-button @click="addInfo" v-hasPermi="['member:user:create']">
+          <Icon class="mr-5px" icon="eq:add" />
+          新增
+        </el-button>
         <el-button plain type="danger" @click="toggleExpandAll">
           <Icon class="mr-5px" icon="ep:sort" />
           展开/折叠
@@ -89,14 +92,14 @@
       row-key="id"
     >
       <el-table-column :show-overflow-tooltip="true"  align="center" label="用户编号" prop="id" width="120px" />
-      <el-table-column align="center" label="头像" prop="avatar" width="80px">
+      <!-- <el-table-column align="center" label="头像" prop="avatar" width="80px">
         <template #default="scope">
           <img :src="scope.row.avatar" style="width: 40px" />
         </template>
-      </el-table-column>
-      <el-table-column align="center" label="手机号" prop="mobile" width="120px" />
+      </el-table-column> -->
+      <el-table-column align="center" label="手机号" prop="mobile" width="160px" />
       <el-table-column align="center" label="昵称" prop="nickname" width="180px" />
-      <el-table-column align="center" label="套餐" prop="comboName" width="180px" />
+      <el-table-column align="center" label="套餐" prop="comboName" width="200px" />
       <el-table-column align="center" label="等级" prop="levelName" width="100px" />
       <el-table-column align="center" label="分组" prop="groupName" width="100px" />
       <el-table-column
@@ -104,6 +107,7 @@
         align="center"
         label="用户标签"
         prop="tagNames"
+        width="120px"
       >
         <template #default="scope">
           <el-tag v-for="(tagName, index) in scope.row.tagNames" :key="index" class="mr-3px">
@@ -111,7 +115,7 @@
           </el-tag>
         </template>
       </el-table-column>
-      <el-table-column align="center" label="积分" prop="point" width="100px" />
+      <!-- <el-table-column align="center" label="积分" prop="point" width="100px" /> -->
       <el-table-column align="center" label="状态" prop="status" width="100px">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
@@ -141,7 +145,8 @@
         <template #default="scope">
           <div class="flex items-center justify-center">
             <el-button link type="primary" @click="openDetail(scope.row.id)">详情</el-button>
-            <el-dropdown
+            <el-button v-if="checkPermi(['member:user:update'])" link type="primary" @click="handleCommand('handleUpdate',scope.row)">编辑</el-button>
+            <!-- <el-dropdown
               v-hasPermi="[
                 'member:user:update',
                 'member:user:update-level',
@@ -182,18 +187,11 @@
                   </el-dropdown-item>
                 </el-dropdown-menu>
               </template>
-            </el-dropdown>
+            </el-dropdown> -->
           </div>
         </template>
       </el-table-column>
     </el-table>
-    <!-- 分页 -->
-<!--    <Pagination-->
-<!--      v-model:limit="queryParams.pageSize"-->
-<!--      v-model:page="queryParams.pageNo"-->
-<!--      :total="total"-->
-<!--      @pagination="getList"-->
-<!--    />-->
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改 -->
@@ -204,8 +202,6 @@
   <UserPointUpdateForm ref="updatePointFormRef" @success="getList" />
   <!-- 修改用户余额弹窗 -->
   <UserBalanceUpdateForm ref="UpdateBalanceFormRef" @success="getList" />
-  <!-- 发送优惠券弹窗 -->
-  <CouponSendForm ref="couponSendFormRef" />
 </template>
 <script lang="ts" setup>
 import { dateFormatter } from '@/utils/formatTime'
@@ -213,12 +209,9 @@ import * as UserApi from '@/api/member/user'
 import { DICT_TYPE } from '@/utils/dict'
 import UserForm from './UserForm.vue'
 import MemberTagSelect from '@/views/member/tag/components/MemberTagSelect.vue'
-// import MemberLevelSelect from '@/views/member/level/components/MemberLevelSelect.vue'
 import MemberGroupSelect from '@/views/member/group/components/MemberGroupSelect.vue'
-// import UserLevelUpdateForm from './components/UserLevelUpdateForm.vue'
 import UserPointUpdateForm from './components/UserPointUpdateForm.vue'
 import UserBalanceUpdateForm from './components/UserBalanceUpdateForm.vue'
-import { CouponSendForm } from '@/views/mall/promotion/coupon/components'
 import { checkPermi } from '@/utils/permission'
 import {handleTree} from "@/utils/tree";
 
@@ -283,15 +276,18 @@ const openForm = (type: string, id?: number) => {
 }
 
 /** 发送优惠券 */
-const couponSendFormRef = ref()
-const openCoupon = () => {
-  if (selectedIds.value.length === 0) {
-    message.warning('请选择要发送优惠券的用户')
-    return
-  }
-  couponSendFormRef.value.open(selectedIds.value)
+// const couponSendFormRef = ref()
+// const openCoupon = () => {
+//   if (selectedIds.value.length === 0) {
+//     message.warning('请选择要发送优惠券的用户')
+//     return
+//   }
+//   couponSendFormRef.value.open(selectedIds.value)
+// }
+/**新增 */
+const addInfo = ()=>{
+  openForm('create')
 }
-
 /** 操作分发 */
 const handleCommand = (command: string, row: UserApi.UserVO) => {
   switch (command) {