From d8ae565b4ae8c536396d74e9473e3726083c3c84 Mon Sep 17 00:00:00 2001 From: dzq <dzq@ys.com> Date: Wed, 2 Apr 2025 09:34:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/qy/qyUser.ts | 12 +- src/api/system/dept.ts | 1 + src/views/system/qyUser/hook.tsx | 114 ------------- src/views/user/qy/BalanceEditModal.vue | 78 +++++++++ .../{system/qyUser => user/qy}/UserDetail.vue | 0 src/views/user/qy/hook.tsx | 151 ++++++++++++++++++ .../{system/qyUser => user/qy}/index.vue | 62 +++---- src/views/{system/qyUser => user/qy}/tree.vue | 0 8 files changed, 273 insertions(+), 145 deletions(-) delete mode 100644 src/views/system/qyUser/hook.tsx create mode 100644 src/views/user/qy/BalanceEditModal.vue rename src/views/{system/qyUser => user/qy}/UserDetail.vue (100%) create mode 100644 src/views/user/qy/hook.tsx rename src/views/{system/qyUser => user/qy}/index.vue (73%) rename src/views/{system/qyUser => user/qy}/tree.vue (100%) diff --git a/src/api/qy/qyUser.ts b/src/api/qy/qyUser.ts index 99c41a1..b952f9e 100644 --- a/src/api/qy/qyUser.ts +++ b/src/api/qy/qyUser.ts @@ -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 { diff --git a/src/api/system/dept.ts b/src/api/system/dept.ts index 52b370a..54c8509 100644 --- a/src/api/system/dept.ts +++ b/src/api/system/dept.ts @@ -12,6 +12,7 @@ export interface DeptQuery extends BaseQuery { export interface DeptDTO { createTime?: Date; id?: number; + departmentId?: string; deptName?: string; email?: string; leaderName?: string; diff --git a/src/views/system/qyUser/hook.tsx b/src/views/system/qyUser/hook.tsx deleted file mode 100644 index fe95bc5..0000000 --- a/src/views/system/qyUser/hook.tsx +++ /dev/null @@ -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 - }; -} diff --git a/src/views/user/qy/BalanceEditModal.vue b/src/views/user/qy/BalanceEditModal.vue new file mode 100644 index 0000000..3c36495 --- /dev/null +++ b/src/views/user/qy/BalanceEditModal.vue @@ -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> \ No newline at end of file diff --git a/src/views/system/qyUser/UserDetail.vue b/src/views/user/qy/UserDetail.vue similarity index 100% rename from src/views/system/qyUser/UserDetail.vue rename to src/views/user/qy/UserDetail.vue diff --git a/src/views/user/qy/hook.tsx b/src/views/user/qy/hook.tsx new file mode 100644 index 0000000..8201bf8 --- /dev/null +++ b/src/views/user/qy/hook.tsx @@ -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 + }; +} diff --git a/src/views/system/qyUser/index.vue b/src/views/user/qy/index.vue similarity index 73% rename from src/views/system/qyUser/index.vue rename to src/views/user/qy/index.vue index ad20865..9a32b6a 100644 --- a/src/views/system/qyUser/index.vue +++ b/src/views/user/qy/index.vue @@ -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-button type="primary" size="small" class="detail-btn" @click="handleViewDetail(item)"> - 查看详情 - </el-button> + <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; diff --git a/src/views/system/qyUser/tree.vue b/src/views/user/qy/tree.vue similarity index 100% rename from src/views/system/qyUser/tree.vue rename to src/views/user/qy/tree.vue