企业微信用户管理
This commit is contained in:
parent
e8672104a4
commit
d8ae565b4a
|
@ -50,6 +50,8 @@ export interface QyUserDTO {
|
|||
corpid?: string;
|
||||
/** 应用ID(导出列:应用ID) */
|
||||
appid?: string;
|
||||
/** 用户余额 */
|
||||
balance?: number;
|
||||
}
|
||||
|
||||
export interface QyUserQuery extends BasePageQuery {
|
||||
|
@ -62,10 +64,12 @@ export interface QyUserQuery extends BasePageQuery {
|
|||
}
|
||||
|
||||
export interface AddQyUserCommand {
|
||||
name: string;
|
||||
mobile: string;
|
||||
department: string;
|
||||
corpid: string;
|
||||
name?: string;
|
||||
mobile?: string;
|
||||
department?: string;
|
||||
corpid?: string;
|
||||
/** 用户余额 */
|
||||
balance?: number;
|
||||
}
|
||||
|
||||
export interface UpdateQyUserCommand extends AddQyUserCommand {
|
||||
|
|
|
@ -12,6 +12,7 @@ export interface DeptQuery extends BaseQuery {
|
|||
export interface DeptDTO {
|
||||
createTime?: Date;
|
||||
id?: number;
|
||||
departmentId?: string;
|
||||
deptName?: string;
|
||||
email?: string;
|
||||
leaderName?: string;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
|
@ -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>
|
|
@ -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
|
||||
};
|
||||
}
|
|
@ -2,19 +2,11 @@
|
|||
import { ref, watch } from "vue";
|
||||
import tree from "./tree.vue";
|
||||
import { useHook } from "./hook";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
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 Refresh from "@iconify-icons/ep/refresh";
|
||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
|
||||
defineOptions({
|
||||
name: "QyUser"
|
||||
|
@ -23,14 +15,16 @@ defineOptions({
|
|||
const formRef = ref();
|
||||
const {
|
||||
searchFormParams,
|
||||
onSearch,
|
||||
pageLoading,
|
||||
dataList,
|
||||
pagination,
|
||||
buttonClass,
|
||||
onSearch,
|
||||
resetForm,
|
||||
getList,
|
||||
handleViewDetail
|
||||
resetForm,
|
||||
handleViewDetail,
|
||||
handleModifyBalance,
|
||||
balanceVisible,
|
||||
selectedUser
|
||||
} = useHook();
|
||||
|
||||
watch(
|
||||
|
@ -45,6 +39,7 @@ watch(
|
|||
<div class="main">
|
||||
<tree class="w-[17%] float-left" v-model="searchFormParams.mainDepartment" />
|
||||
<div class="float-right w-[82%]">
|
||||
<BalanceEditModal v-model:visible="balanceVisible" :row="selectedUser" @refresh="getList" />
|
||||
<el-form ref="formRef" :inline="true" :model="searchFormParams"
|
||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]">
|
||||
<el-form-item label="姓名:" prop="name">
|
||||
|
@ -62,19 +57,29 @@ watch(
|
|||
|
||||
<div class="grid-container">
|
||||
<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">
|
||||
<div class="card-content">
|
||||
<el-avatar :size="60" :src="item.avatar" class="avatar" />
|
||||
<div class="user-info">
|
||||
<div class="name">{{ item.name }}</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>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<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-col>
|
||||
</el-row>
|
||||
|
@ -117,8 +122,6 @@ watch(
|
|||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
|
||||
.name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
|
@ -131,6 +134,14 @@ watch(
|
|||
color: #909399;
|
||||
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 {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: relative;
|
||||
background: var(--el-bg-color);
|
||||
padding: 12px 20px;
|
||||
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.05);
|
||||
z-index: 10;
|
||||
padding: 12px 12px;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
|
||||
:deep(.el-pagination) {
|
||||
margin: 0;
|
Loading…
Reference in New Issue