家庭成员

This commit is contained in:
Flow 2025-06-06 15:34:23 +08:00
parent 7a9b132a90
commit 461edb3b4e
5 changed files with 314 additions and 351 deletions

View File

@ -1,53 +1,73 @@
import request from '@/config/axios'
// 用户基本信息 VO
export interface PersonVO {
id: number // 主键ID
phone: string // 手机号(登录账号)
password: string // 密码
name: string // 姓名
address: string // 地址
orgid: number // 机构ID
orgname: string // 机构名称
familyid: string // 家庭组号
familyrelation: number // 家庭关系1-本人2-兄弟3-父亲4-母亲5-子女6-其他
isvip: number // 是否会员0-否1-是
idcard: string // 身份证号
createtime: Date // 创建时间
updatetime: Date // 更新时间
createby: string // 创建人
updateby: string // 更新人
}
// 用户基本信息 API
export const PersonApi = {
// 查询用户基本信息分页
getPersonPage: async (params: any) => {
return await request.get({ url: `/system/person/page`, params })
},
// 查询用户基本信息详情
getPerson: async (id: number) => {
return await request.get({ url: `/system/person/get?id=` + id })
},
// 新增用户基本信息
createPerson: async (data: PersonVO) => {
return await request.post({ url: `/system/person/create`, data })
},
// 修改用户基本信息
updatePerson: async (data: PersonVO) => {
return await request.put({ url: `/system/person/update`, data })
},
// 删除用户基本信息
deletePerson: async (id: number) => {
return await request.delete({ url: `/system/person/delete?id=` + id })
},
// 导出用户基本信息 Excel
exportPerson: async (params) => {
return await request.download({ url: `/system/person/export-excel`, params })
},
}
import request from '@/config/axios'
// 用户基本信息 VO
export interface PersonVO {
id: number // 主键ID
phone: string // 手机号(登录账号)
password: string // 密码
name: string // 姓名
address: string // 地址
orgid: number // 机构ID
orgname: string // 机构名称
familyid: string // 家庭组号
familyrelation: number // 家庭关系1-主号2-兄弟3-父亲4-母亲5-子女6-其他
isvip: number // 是否会员0-否1-是
idcard: string // 身份证号
createtime: string // 创建时间
updatetime: string // 更新时间
createby: string // 创建人
updateby: string // 更新人
}
// 用户基本信息 API
export const PersonApi = {
// 查询用户基本信息分页
getPersonPage: async (params: any) => {
return await request.get({ url: `/system/person/page`, params })
},
// 查询用户基本信息详情
getPerson: async (id: number) => {
return await request.get({ url: `/system/person/get?id=` + id })
},
// 新增用户基本信息
createPerson: async (data: PersonVO) => {
return await request.post({ url: `/system/person/create`, data })
},
// 修改用户基本信息
updatePerson: async (data: PersonVO) => {
return await request.put({ url: `/system/person/update`, data })
},
// 删除用户基本信息
deletePerson: async (id: number) => {
return await request.delete({ url: `/system/person/delete?id=` + id })
},
// 导出用户基本信息 Excel
exportPerson: async (params) => {
return await request.download({ url: `/system/person/export-excel`, params })
},
// 根据用户ID更新家庭组号
addMembers: async (id: number, familyid: string) => {
return await request.put({ url: `/system/person/add-members?id=` + id + `&familyid=` + familyid })
},
// 根据用户ID移除家庭组号
removeMembers: async (id: number) => {
return await request.put({ url: `/system/person/remove-members?id=` + id })
},
// 根据家庭组号查询成员
getMembersByFamilyid: async (familyid: string) => {
return await request.get({ url: `/system/person/get-members-by-familyid?familyid=` + familyid })
},
// 根据查询没有家庭组号的用户
getPersonNoFamilyid: async (params: any) => {
return await request.get({ url: `/system/person/page-no-familyid`, params })
},
}

View File

@ -7,61 +7,45 @@
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="手机号" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="formData.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="formData.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="formData.address" placeholder="请输入地址" />
</el-form-item>
<el-form-item label="机构ID" prop="orgid">
<el-input v-model="formData.orgid" placeholder="请输入机构ID" />
</el-form-item>
<el-form-item label="机构名称" prop="orgname">
<el-input v-model="formData.orgname" placeholder="请输入机构名称" />
</el-form-item>
<el-form-item label="家庭组号" prop="familyid">
<el-input v-model="formData.familyid" placeholder="请输入家庭组号" />
</el-form-item>
<el-form-item label="家庭关系" prop="familyrelation">
<el-input v-model="formData.familyrelation" placeholder="请输入家庭关系" />
</el-form-item>
<el-form-item label="是否会员" prop="isvip">
<el-select v-model="formData.isvip" placeholder="请选择是否会员">
<el-option label="是" value="1" />
<el-option label="否" value="0" />
</el-select>
</el-form-item>
<el-form-item label="身份证号" prop="idcard">
<el-input v-model="formData.idcard" placeholder="请输入身份证号" />
</el-form-item>
<el-form-item label="创建时间" prop="createtime">
<el-date-picker
v-model="formData.createtime"
type="date"
value-format="x"
placeholder="选择创建时间"
/>
</el-form-item>
<el-form-item label="更新时间" prop="updatetime">
<el-date-picker
v-model="formData.updatetime"
type="date"
value-format="x"
placeholder="选择更新时间"
/>
</el-form-item>
<el-form-item label="创建人" prop="createby">
<el-input v-model="formData.createby" placeholder="请输入创建人" />
</el-form-item>
<el-form-item label="更新人" prop="updateby">
<el-input v-model="formData.updateby" placeholder="请输入更新人" />
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="手机号" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="formData.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="formData.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="formData.address" placeholder="请输入地址" />
</el-form-item>
<el-form-item label="机构ID" prop="orgid">
<el-input v-model="formData.orgid" placeholder="请输入机构ID" />
</el-form-item>
<el-form-item label="机构名称" prop="orgname">
<el-input v-model="formData.orgname" placeholder="请输入机构名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="家庭组号" prop="familyid">
<el-input v-model="formData.familyid" placeholder="请输入家庭组号" />
</el-form-item>
<el-form-item label="家庭关系" prop="familyrelation">
<el-input v-model="formData.familyrelation" placeholder="请输入家庭关系" />
</el-form-item>
<el-form-item label="身份证号" prop="idcard">
<el-input v-model="formData.idcard" placeholder="请输入身份证号" />
</el-form-item>
<el-form-item label="创建人" prop="createby">
<el-input v-model="formData.createby" placeholder="请输入创建人" />
</el-form-item>
<el-form-item label="更新人" prop="updateby">
<el-input v-model="formData.updateby" placeholder="请输入更新人" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
@ -71,6 +55,7 @@
</template>
<script setup lang="ts">
import { PersonApi, PersonVO } from '@/api/person'
import dayjs from 'dayjs'
/** 用户基本信息 表单 */
defineOptions({ name: 'PersonForm' })
@ -100,7 +85,7 @@ const formData = ref({
updateby: undefined,
})
const formRules = reactive({
phone: [{ required: true, message: '手机号(登录账号)不能为空', trigger: 'blur' }],
phone: [{ required: true, message: '手机号不能为空', trigger: 'blur' }],
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],
name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
orgid: [{ required: true, message: '机构ID不能为空', trigger: 'blur' }],
@ -135,10 +120,15 @@ const submitForm = async () => {
formLoading.value = true
try {
const data = formData.value as unknown as PersonVO
const datetime = dayjs().format('YYYY-MM-DD HH:mm:ss')
if (formType.value === 'create') {
//
data.createtime = datetime
await PersonApi.createPerson(data)
message.success(t('common.createSuccess'))
} else {
//
data.updatetime = datetime
await PersonApi.updatePerson(data)
message.success(t('common.updateSuccess'))
}

View File

@ -13,21 +13,21 @@
>
<Icon icon="ep:plus" /> 添加成员
</el-button>
<span class="member-count">当前成员{{ memberList.length }} </span>
</div>
<el-table v-loading="loading" :data="memberList">
<el-table-column label="编号" align="center" prop="id" width="60" />
<el-table-column label="姓名" align="center" prop="name" width="120" />
<el-table-column label="手机号码" align="center" prop="mobile" width="140" />
<el-table-column label="年龄" align="center" prop="age" width="80" />
<el-table-column label="性别" align="center" prop="gender" width="80">
<template #default="scope">
{{ scope.row.gender === 'male' ? '男' : '女' }}
</template>
</el-table-column>
<el-table-column label="关系" align="center" prop="relation" width="100">
<template #default="scope">
{{ scope.row.relation }}
<el-tag :type="scope.row.gender === 'male' ? 'primary' : 'success'">
{{ scope.row.gender === 'male' ? '男' : '女' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="关系" align="center" prop="relation" width="100" />
<el-table-column label="操作" align="center" width="120">
<template #default="scope">
<el-button
@ -45,7 +45,6 @@
<!-- 添加成员抽屉 -->
<member-drawer
v-model="showTransfer"
:all-members="allMembers"
@confirm="handleAddMembers"
/>
</template>
@ -54,6 +53,8 @@
import { dateFormatter } from '@/utils/formatTime'
import PersonForm from './PersonFrom.vue'
import MemberDrawer from './memberdrawer.vue'
import { PersonApi, PersonVO } from '@/api/person'
import { ElMessage } from 'element-plus'
defineOptions({ name: 'PersonMember' })
@ -62,7 +63,6 @@ const showTransfer = ref(false)
const loading = ref(false)
const memberList = ref<FamilyMember[]>([])
const currentMember = ref<FamilyMember | null>(null)
const selectedMembers = ref<FamilyMember[]>([])
const formRef = ref()
interface FamilyMember {
@ -74,211 +74,116 @@ interface FamilyMember {
relation: string
address: string
createTime: string
}
//
const allMembers = ref<FamilyMember[]>([
{
id: 5,
name: '赵六',
mobile: '13800138005',
age: 30,
gender: 'male',
relation: '父亲',
address: '北京市海淀区中关村大街2号',
createTime: '2024-03-20 10:00:00'
},
{
id: 6,
name: '钱七',
mobile: '13800138006',
age: 28,
gender: 'female',
relation: '母亲',
address: '北京市海淀区中关村大街3号',
createTime: '2024-03-20 10:00:00'
},
{
id: 7,
name: '孙八',
mobile: '13800138007',
age: 25,
gender: 'male',
relation: '兄弟',
address: '北京市海淀区中关村大街4号',
createTime: '2024-03-20 10:00:00'
},
{
id: 8,
name: '周九',
mobile: '13800138008',
age: 22,
gender: 'female',
relation: '姐妹',
address: '北京市海淀区中关村大街5号',
createTime: '2024-03-20 10:00:00'
},
{
id: 9,
name: '吴十',
mobile: '13800138009',
age: 20,
gender: 'male',
relation: '兄弟',
address: '北京市海淀区中关村大街6号',
createTime: '2024-03-20 10:00:00'
},
{
id: 10,
name: '郑十一',
mobile: '13800138010',
age: 18,
gender: 'female',
relation: '姐妹',
address: '北京市海淀区中关村大街7号',
createTime: '2024-03-20 10:00:00'
},
{
id: 11,
name: '王十二',
mobile: '13800138011',
age: 16,
gender: 'male',
relation: '兄弟',
address: '北京市海淀区中关村大街8号',
createTime: '2024-03-20 10:00:00'
},
{
id: 12,
name: '赵十三',
mobile: '13800138010',
age: 48,
gender: 'female',
relation: '舅妈',
address: '北京市朝阳区建国路10号',
createTime: '2024-03-20 10:00:00'
},
{
id: 13,
name: '赵十四',
mobile: '13800138010',
age: 48,
gender: 'female',
relation: '舅妈',
address: '北京市朝阳区建国路10号',
createTime: '2024-03-20 10:00:00'
},
{
id: 14,
name: '赵十五',
mobile: '13800138010',
age: 48,
gender: 'male',
relation: '舅妈',
address: '北京市朝阳区建国路10号',
createTime: '2024-03-20 10:00:00'
}
])
// -
const mockMemberData = {
1: [
{
id: 4,
name: '张小明',
mobile: '13800138004',
age: 12,
gender: 'male',
relation: '儿子',
address: '北京市朝阳区建国路88号',
createTime: '2024-03-20 10:00:00'
}
],
2: [],
3: [],
4: []
familyid: string
familyrelation: number
}
const open = (member: FamilyMember) => {
currentMember.value = member
memberList.value = [] //
dialogVisible.value = true
getMemberList()
}
const getMemberList = async () => {
if (!currentMember.value?.familyid) return
loading.value = true
try {
// API
await new Promise(resolve => setTimeout(resolve, 500))
if (currentMember.value) {
memberList.value = mockMemberData[currentMember.value.id] || []
}
const res = await PersonApi.getMembersByFamilyid(currentMember.value.familyid)
memberList.value = res.map((item: PersonVO) => ({
id: item.id,
name: item.name,
mobile: item.phone,
relation: getFamilyRelation(item.familyrelation),
address: item.address || '',
createTime: item.createtime,
familyid: item.familyid,
familyrelation: item.familyrelation
}))
} catch (error) {
console.error('获取成员列表失败:', error)
} finally {
loading.value = false
}
}
//
const isSelectable = (row: FamilyMember) => {
return selectedMembers.value.length < 5 || selectedMembers.value.some(item => item.id === row.id)
}
//
const handleSelectionChange = (selection: FamilyMember[]) => {
if (selection.length > 5) {
// 55
selectedMembers.value = selection.slice(0, 5)
} else {
selectedMembers.value = selection
//
const getFamilyRelation = (relation: number): string => {
const relationMap: Record<number, string> = {
1: '主号',
2: '兄弟',
3: '父亲',
4: '母亲',
5: '子女',
6: '其他'
}
return relationMap[relation] || '其他'
}
/** 添加成员 */
const handleAddMembers = async (members: FamilyMember[]) => {
if (!currentMember.value) return
// API
await new Promise(resolve => setTimeout(resolve, 500))
//
if (!mockMemberData[currentMember.value.id]) {
mockMemberData[currentMember.value.id] = []
// 5
if (memberList.value.length + members.length > 5) {
ElMessage.warning('家庭成员不能超过5人')
return
}
mockMemberData[currentMember.value.id].push(...members)
//
await getMemberList()
try {
loading.value = true
// API
for (const member of members) {
await PersonApi.addMembers(member.id, currentMember.value.familyid)
}
//
memberList.value.push(...members)
} finally {
loading.value = false
}
}
/** 移除成员 */
const handleRemove = async (row: FamilyMember) => {
if (!currentMember.value) return
// API
await new Promise(resolve => setTimeout(resolve, 500))
//
const index = mockMemberData[currentMember.value.id].findIndex(
member => member.id === row.id
)
if (index > -1) {
mockMemberData[currentMember.value.id].splice(index, 1)
//
if (row.familyrelation === 1) {
ElMessage.warning('主号不允许移除')
return
}
//
await getMemberList()
}
/** 添加/修改操作 */
const openForm = (type: string, id?: number) => {
formRef.value?.open(type, id)
}
const handleSuccess = () => {
getMemberList()
try {
loading.value = true
// API
await PersonApi.removeMembers(row.id)
//
const index = memberList.value.findIndex(member => member.id === row.id)
if (index > -1) {
memberList.value.splice(index, 1)
}
} finally {
loading.value = false
}
}
defineExpose({
open
})
</script>
<style scoped>
.mb-15px {
display: flex;
align-items: center;
justify-content: space-between;
}
.member-count {
color: #409EFF;
font-size: 14px;
}
</style>

View File

@ -107,20 +107,6 @@
:show-overflow-tooltip="true"
min-width="180"
/>
<el-table-column
label="创建时间"
align="center"
prop="createtime"
:formatter="dateFormatter"
width="160"
/>
<el-table-column
label="更新时间"
align="center"
prop="updatetime"
:formatter="dateFormatter"
width="160"
/>
<el-table-column
label="创建人"
align="center"
@ -248,7 +234,7 @@ const handleSuccess = () => {
/** 获取家庭关系标签 */
const getFamilyRelationLabel = (relation: number) => {
const relationMap = {
1: '本人',
1: '主号',
2: '兄弟',
3: '父亲',
4: '母亲',

View File

@ -7,30 +7,39 @@
>
<div class="drawer-content">
<div class="search-box">
<el-input v-model="searchForm.name" placeholder="请输入姓名" clearable style="width: 180px; margin-right: 10px;" />
<el-input v-model="searchForm.mobile" placeholder="请输入手机号码" clearable style="width: 180px; margin-right: 10px;" />
</div>
<div class="drawer-header">
<span class="selected-count">已选择 {{ selectedMembers.length }}/5 </span>
<el-input v-model="queryParams.name" placeholder="请输入姓名" clearable style="width: 180px; margin-right: 10px;" />
<el-input v-model="queryParams.mobile" placeholder="请输入手机号码" clearable style="width: 180px; margin-right: 10px;" />
<el-button type="primary" @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 搜索
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> 重置
</el-button>
</div>
<div class="table-container">
<el-table
:data="filteredMembers"
:data="list"
style="width: 100%"
@selection-change="handleSelectionChange"
v-loading="loading"
>
<el-table-column type="selection" width="55" :selectable="isSelectable" />
<el-table-column type="selection" width="55" />
<el-table-column label="编号" prop="id" />
<el-table-column label="姓名" prop="name" />
<el-table-column label="性别" prop="gender">
<template #default="scope">
<el-tag :type="scope.row.gender === 'male' ? 'primary' : 'success'">
{{ scope.row.gender === 'male' ? '男' : '女' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="年龄" prop="age" />
<el-table-column label="手机号码" prop="mobile" />
</el-table>
<!-- 分页控件 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="queryParams.pageNo"
v-model:page-size="queryParams.pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
<div class="drawer-footer">
<el-button @click="handleCancel"> </el-button>
@ -43,7 +52,9 @@
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { ref, computed, watch, onMounted } from 'vue'
import { PersonApi, type PersonVO } from '@/api/person'
import { Icon } from '@/components/Icon'
interface FamilyMember {
id: number
@ -58,7 +69,6 @@ interface FamilyMember {
const props = defineProps<{
modelValue: boolean
allMembers: FamilyMember[]
}>()
const emit = defineEmits<{
@ -68,23 +78,86 @@ const emit = defineEmits<{
const visible = ref(props.modelValue)
const selectedMembers = ref<FamilyMember[]>([])
const searchForm = ref({
const list = ref<FamilyMember[]>([])
const loading = ref(false)
const total = ref(0)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: '',
mobile: ''
})
//
const filteredMembers = computed(() => {
return props.allMembers.filter(member => {
const nameMatch = !searchForm.value.name || member.name.includes(searchForm.value.name)
const mobileMatch = !searchForm.value.mobile || member.mobile.includes(searchForm.value.mobile)
return nameMatch && mobileMatch
})
})
//
const getList = async () => {
try {
loading.value = true
const res = await PersonApi.getPersonNoFamilyid(queryParams)
if (res.list) {
list.value = res.list.map((item: PersonVO) => ({
id: item.id,
name: item.name,
mobile: item.phone,
relation: getFamilyRelation(item.familyrelation),
address: item.address || '',
createTime: item.createtime,
familyid: item.familyid,
familyrelation: item.familyrelation
}))
total.value = res.total
}
} catch (error) {
console.error('获取成员数据失败:', error)
} finally {
loading.value = false
}
}
//
const getFamilyRelation = (relation: number): string => {
const relationMap: Record<number, string> = {
1: '本人',
2: '兄弟',
3: '父亲',
4: '母亲',
5: '子女',
6: '其他'
}
return relationMap[relation] || '其他'
}
//
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
//
const resetQuery = () => {
queryParams.pageNo = 1
queryParams.name = ''
queryParams.mobile = ''
getList()
}
//
const handleSizeChange = (val: number) => {
queryParams.pageSize = val
getList()
}
//
const handleCurrentChange = (val: number) => {
queryParams.pageNo = val
getList()
}
// visible
watch(() => props.modelValue, (val) => {
visible.value = val
if (val) {
getList()
}
})
// visible
@ -94,17 +167,12 @@ watch(visible, (val) => {
//
const isSelectable = (row: FamilyMember) => {
return selectedMembers.value.length < 5 || selectedMembers.value.some(item => item.id === row.id)
return true
}
//
const handleSelectionChange = (selection: FamilyMember[]) => {
if (selection.length > 5) {
// 55
selectedMembers.value = selection.slice(0, 5)
} else {
selectedMembers.value = selection
}
selectedMembers.value = selection
}
//
@ -118,16 +186,11 @@ const handleConfirm = () => {
emit('confirm', selectedMembers.value)
handleCancel()
}
//
const handleSearch = () => {
//
}
</script>
<style scoped>
.drawer-content {
height: 89%;
height: 100%;
display: flex;
flex-direction: column;
}
@ -135,17 +198,8 @@ const handleSearch = () => {
.search-box {
padding: 16px;
border-bottom: 1px solid #e4e7ed;
}
.drawer-header {
display: flex;
justify-content: flex-end;
padding: 10px 16px;
}
.selected-count {
color: #409EFF;
font-size: 14px;
align-items: center;
}
.table-container {
@ -153,10 +207,12 @@ const handleSearch = () => {
overflow: auto;
height: calc(100vh - 250px);
margin: 0 16px;
display: flex;
flex-direction: column;
}
:deep(.el-table) {
height: 100%;
flex: 1;
}
:deep(.el-table__header-wrapper) {
@ -169,6 +225,12 @@ const handleSearch = () => {
overflow-y: auto;
}
.pagination-container {
padding: 16px 0;
display: flex;
justify-content: flex-end;
}
.drawer-footer {
padding: 16px;
text-align: right;