企业微信用户管理

This commit is contained in:
dzq 2025-04-02 09:34:17 +08:00
parent e8672104a4
commit d8ae565b4a
8 changed files with 273 additions and 145 deletions

View File

@ -50,6 +50,8 @@ export interface QyUserDTO {
corpid?: string; corpid?: string;
/** 应用ID导出列应用ID */ /** 应用ID导出列应用ID */
appid?: string; appid?: string;
/** 用户余额 */
balance?: number;
} }
export interface QyUserQuery extends BasePageQuery { export interface QyUserQuery extends BasePageQuery {
@ -62,10 +64,12 @@ export interface QyUserQuery extends BasePageQuery {
} }
export interface AddQyUserCommand { export interface AddQyUserCommand {
name: string; name?: string;
mobile: string; mobile?: string;
department: string; department?: string;
corpid: string; corpid?: string;
/** 用户余额 */
balance?: number;
} }
export interface UpdateQyUserCommand extends AddQyUserCommand { export interface UpdateQyUserCommand extends AddQyUserCommand {

View File

@ -12,6 +12,7 @@ export interface DeptQuery extends BaseQuery {
export interface DeptDTO { export interface DeptDTO {
createTime?: Date; createTime?: Date;
id?: number; id?: number;
departmentId?: string;
deptName?: string; deptName?: string;
email?: string; email?: string;
leaderName?: string; leaderName?: string;

View File

@ -1,114 +0,0 @@
import dayjs from "dayjs";
import { message } from "@/utils/message";
import {
QyUserQuery,
getQyUserListApi
} from "@/api/qy/qyUser";
import { ElMessageBox } from "element-plus";
import { type PaginationProps } from "@pureadmin/table";
import { reactive, ref, computed, onMounted, toRaw, h } from "vue";
import { CommonUtils } from "@/utils/common";
import { addDialog } from "@/components/ReDialog";
import userDetail from "./UserDetail.vue";
import { handleTree, setDisabledForTreeOptions } from "@/utils/tree";
import { getQyDeptListApi } from "@/api/system/dept";
import { getPostListApi } from "@/api/system/post";
import { getRoleListApi } from "@/api/system/role";
import { useWxStore } from "@/store/modules/wx";
export function useHook() {
const wxStore = useWxStore();
const searchFormParams = reactive<QyUserQuery>({
/** 姓名(导出列:姓名) */
name: undefined,
/** 手机号(导出列:联系方式) */
mobile: undefined,
corpid: wxStore.corpid,
mainDepartment: undefined,
});
const formRef = ref();
const timeRange = ref<[string, string]>();
const dataList = ref([]);
const pageLoading = ref(true);
const switchLoadMap = ref({});
const pagination = reactive<PaginationProps>({
total: 0,
pageSize: 8,
currentPage: 1,
background: true
});
const deptTreeList = ref([]);
const postOptions = ref([]);
const roleOptions = ref([]);
const buttonClass = computed(() => {
return [
"!h-[20px]",
"reset-margin",
"!text-gray-500",
"dark:!text-white",
"dark:hover:!text-primary"
];
});
async function onSearch() {
// 点击搜索的时候 需要重置分页
pagination.currentPage = 1;
getList();
}
async function getList() {
CommonUtils.fillPaginationParams(searchFormParams, pagination);
CommonUtils.fillTimeRangeParams(searchFormParams, timeRange.value);
pageLoading.value = true;
const { data } = await getQyUserListApi(toRaw(searchFormParams)).finally(
() => {
pageLoading.value = false;
}
);
dataList.value = data.rows;
pagination.total = data.total;
}
const resetForm = formEl => {
if (!formEl) return;
formEl.resetFields();
onSearch();
};
onMounted(async () => {
onSearch();
const deptResponse = await getQyDeptListApi(wxStore.corpid);
deptTreeList.value = await setDisabledForTreeOptions(
handleTree(deptResponse.data),
"status"
);
const postResponse = await getPostListApi({});
postOptions.value = postResponse.data.rows;
const roleResponse = await getRoleListApi({});
roleOptions.value = roleResponse.data.rows;
});
const handleViewDetail = (row: any) => {
};
return {
searchFormParams,
pageLoading,
dataList,
pagination,
buttonClass,
onSearch,
resetForm,
getList,
handleViewDetail
};
}

View File

@ -0,0 +1,78 @@
<script setup lang="ts">
import { ref, reactive, watch } from "vue";
import { ElMessage } from "element-plus";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { updateQyUserApi } from "@/api/qy/qyUser";
import Confirm from "@iconify-icons/ep/check";
import type { FormRules } from 'element-plus';
const props = defineProps({
visible: {
type: Boolean,
default: false
},
row: {
type: Object,
default: null
}
});
const emit = defineEmits(["update:visible", "refresh"]);
const formRef = ref();
const formData = reactive({
balance: 0
});
const rules = reactive<FormRules>({
balance: [
{ required: true, message: "请输入金额", trigger: "blur" },
{ type: 'number', message: '必须为数字类型' },
{ validator: (_, v, cb) => v >= 0 ? cb() : cb(new Error('金额不能小于0')), trigger: 'blur' }
]
});
const handleConfirm = async () => {
try {
await formRef.value.validate();
await updateQyUserApi(props.row.id, {
id: props.row.id,
balance: formData.balance
});
ElMessage.success("余额修改成功");
emit("refresh");
closeDialog();
} catch (error) {
console.error("表单提交失败", error);
}
};
const closeDialog = () => {
formRef.value.resetFields();
emit("update:visible", false);
};
watch(() => props.row, (val) => {
if (val) {
formData.balance = val.balance || 0;
}
}, { immediate: true });
</script>
<template>
<el-dialog title="修改余额" :model-value="visible" width="500px" @close="closeDialog">
<el-form ref="formRef" :model="formData" :rules="rules" label-width="80px">
<el-form-item label="金额" prop="balance">
<el-input-number v-model="formData.balance" :precision="2" :step="0.1" :min="0" controls-position="right"
class="!w-[200px]" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" :icon="useRenderIcon(Confirm)" @click="handleConfirm">
确认
</el-button>
</template>
</el-dialog>
</template>

151
src/views/user/qy/hook.tsx Normal file
View File

@ -0,0 +1,151 @@
import {
QyUserDTO,
QyUserQuery,
getQyUserListApi,
updateQyUserApi
} from "@/api/qy/qyUser";
import { ElMessage } from "element-plus";
import { type PaginationProps } from "@pureadmin/table";
import { reactive, ref, onMounted, toRaw, h, onBeforeUnmount } from "vue";
import { CommonUtils } from "@/utils/common";
import { handleTree, setDisabledForTreeOptions } from "@/utils/tree";
import { getQyDeptListApi } from "@/api/system/dept";
import { getPostListApi } from "@/api/system/post";
import { getRoleListApi } from "@/api/system/role";
import { useWxStore } from "@/store/modules/wx";
/**
*
* //
*/
export function useHook() {
const wxStore = useWxStore(); // 微信相关状态管理
// 搜索表单参数
const searchFormParams = reactive<QyUserQuery>({
/** 姓名(导出列:姓名) */
name: undefined,
/** 手机号(导出列:联系方式) */
mobile: undefined,
corpid: wxStore.corpid, // 企业ID
mainDepartment: undefined, // 主部门
});
const timeRange = ref<[string, string]>(); // 时间范围选择
// 列表相关状态
const dataList = ref([]); // 用户列表数据
const pageLoading = ref(true); // 加载状态
const pagination = reactive<PaginationProps>({ // 分页配置
total: 0,
pageSize: 8,
currentPage: 1,
background: true
});
// 余额修改相关状态
const BalanceEditModal = ref(null); // 余额编辑模态框引用
const balanceVisible = ref(false); // 余额弹窗可见性
const currentBalance = ref(0); // 当前编辑的余额值
const selectedUserId = ref<number>(); // 当前选中的用户ID
const selectedUser = ref<QyUserDTO>(); // 当前选中的用户对象
// 下拉选项数据
const deptTreeList = ref([]); // 部门树形数据
const postOptions = ref([]); // 岗位选项
const roleOptions = ref([]); // 角色选项
/**
*
*
*/
async function onSearch() {
pagination.currentPage = 1;
getList();
}
/**
*
* API
*/
async function getList() {
CommonUtils.fillPaginationParams(searchFormParams, pagination);
CommonUtils.fillTimeRangeParams(searchFormParams, timeRange.value);
pageLoading.value = true;
const { data } = await getQyUserListApi(toRaw(searchFormParams)).finally(
() => {
pageLoading.value = false;
}
);
dataList.value = data.rows;
pagination.total = data.total;
}
/**
*
* @param formEl
*/
const resetForm = formEl => {
if (!formEl) return;
formEl.resetFields();
onSearch();
};
// 组件挂载时执行
onMounted(async () => {
onSearch(); // 初始加载列表数据
// 获取部门树形数据
const deptResponse = await getQyDeptListApi(wxStore.corpid);
deptTreeList.value = await setDisabledForTreeOptions(
handleTree(deptResponse.data),
"status"
);
// 获取岗位选项
const postResponse = await getPostListApi({});
postOptions.value = postResponse.data.rows;
// 获取角色选项
const roleResponse = await getRoleListApi({});
roleOptions.value = roleResponse.data.rows;
});
/**
*
* @param row
*/
const handleViewDetail = (row: any) => {
// TODO: 实现查看详情逻辑
};
/**
*
* @param row
*/
const handleModifyBalance = (row: QyUserDTO) => {
selectedUserId.value = row.id;
currentBalance.value = row.balance || 0;
selectedUser.value = row;
balanceVisible.value = true;
};
// 暴露给模板使用的属性和方法
return {
BalanceEditModal,
searchFormParams,
pageLoading,
dataList,
pagination,
onSearch,
resetForm,
getList,
handleViewDetail,
handleModifyBalance,
balanceVisible,
selectedUser
};
}

View File

@ -2,19 +2,11 @@
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import tree from "./tree.vue"; import tree from "./tree.vue";
import { useHook } from "./hook"; import { useHook } from "./hook";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import BalanceEditModal from "./BalanceEditModal.vue";
import Password from "@iconify-icons/ri/lock-password-line";
import More from "@iconify-icons/ep/more-filled";
import Delete from "@iconify-icons/ep/delete";
import EditPen from "@iconify-icons/ep/edit-pen";
import Download from "@iconify-icons/ep/download";
import Upload from "@iconify-icons/ep/upload";
import Search from "@iconify-icons/ep/search"; import Search from "@iconify-icons/ep/search";
import Refresh from "@iconify-icons/ep/refresh"; import Refresh from "@iconify-icons/ep/refresh";
import AddFill from "@iconify-icons/ri/add-circle-line";
import { useUserStoreHook } from "@/store/modules/user";
defineOptions({ defineOptions({
name: "QyUser" name: "QyUser"
@ -23,14 +15,16 @@ defineOptions({
const formRef = ref(); const formRef = ref();
const { const {
searchFormParams, searchFormParams,
onSearch,
pageLoading, pageLoading,
dataList, dataList,
pagination, pagination,
buttonClass,
onSearch,
resetForm,
getList, getList,
handleViewDetail resetForm,
handleViewDetail,
handleModifyBalance,
balanceVisible,
selectedUser
} = useHook(); } = useHook();
watch( watch(
@ -45,6 +39,7 @@ watch(
<div class="main"> <div class="main">
<tree class="w-[17%] float-left" v-model="searchFormParams.mainDepartment" /> <tree class="w-[17%] float-left" v-model="searchFormParams.mainDepartment" />
<div class="float-right w-[82%]"> <div class="float-right w-[82%]">
<BalanceEditModal v-model:visible="balanceVisible" :row="selectedUser" @refresh="getList" />
<el-form ref="formRef" :inline="true" :model="searchFormParams" <el-form ref="formRef" :inline="true" :model="searchFormParams"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"> class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]">
<el-form-item label="姓名:" prop="name"> <el-form-item label="姓名:" prop="name">
@ -62,19 +57,29 @@ watch(
<div class="grid-container"> <div class="grid-container">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col v-for="(item, index) in dataList" :key="index" :xs="24" :sm="12" :md="8" :lg="6"> <el-col v-for="(item, index) in dataList" :key="item.id" :xs="24" :sm="12" :md="8" :lg="6">
<el-card class="user-card"> <el-card class="user-card">
<div class="card-content"> <div class="card-content">
<el-avatar :size="60" :src="item.avatar" class="avatar" /> <el-avatar :size="60" :src="item.avatar" class="avatar" />
<div class="user-info"> <div class="user-info">
<div class="name">{{ item.name }}</div> <div class="name">{{ item.name }}</div>
<div class="gender">性别{{ item.gender === '1' ? '男' : item.gender === '2' ? '女' : '' }}</div> <div class="gender">性别{{ item.gender === '1' ? '男' : item.gender === '2' ? '女' : '' }}</div>
<div class="create-time">创建时间{{ item.createTime }}</div> <div class="create-time">创建时间{{ item.createTimeStr }}</div>
<div class="balance">余额¥{{ item.balance?.toFixed(2) }}</div>
</div> </div>
</div> </div>
<el-button type="primary" size="small" class="detail-btn" @click="handleViewDetail(item)"> <el-row :gutter="10">
查看详情 <el-col :span="12">
</el-button> <el-button type="primary" size="small" class="detail-btn" @click="handleViewDetail(item)">
查看详情
</el-button>
</el-col>
<el-col :span="12">
<el-button type="warning" size="small" class="detail-btn" @click="handleModifyBalance(item)">
修改余额
</el-button>
</el-col>
</el-row>
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
@ -117,8 +122,6 @@ watch(
} }
.user-info { .user-info {
flex: 1;
.name { .name {
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
@ -131,6 +134,14 @@ watch(
color: #909399; color: #909399;
line-height: 1.5; line-height: 1.5;
} }
.balance {
font-family: monospace;
font-size: 14px;
color: #67c23a;
margin-bottom: 6px;
line-height: 1.5;
}
} }
} }
@ -151,14 +162,11 @@ watch(
} }
.pagination-wrapper { .pagination-wrapper {
position: fixed; position: relative;
bottom: 0;
left: 0;
right: 0;
background: var(--el-bg-color); background: var(--el-bg-color);
padding: 12px 20px; padding: 12px 12px;
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.05); margin-top: 20px;
z-index: 10; text-align: center;
:deep(.el-pagination) { :deep(.el-pagination) {
margin: 0; margin: 0;