feat(用户管理): 添加微信头像字段并优化标签功能
- 在Ab98UserDTO接口中添加wxAvatar字段 - 将tagName查询参数改为tagIds以支持多标签筛选 - 重构图标渲染逻辑为通用函数getIconByName - 优化用户详情页标签管理功能,添加标签后自动刷新可用标签列表 - 在用户列表页添加多标签筛选功能 - 调整用户卡片头像显示逻辑,优先显示faceImg,其次显示wxAvatar
This commit is contained in:
parent
b45ca4022a
commit
02bc7e6303
|
|
@ -39,6 +39,8 @@ export interface Ab98UserDTO {
|
||||||
wxNickName?: string;
|
wxNickName?: string;
|
||||||
/** 微信用户openid */
|
/** 微信用户openid */
|
||||||
wxUserOpenid?: string;
|
wxUserOpenid?: string;
|
||||||
|
/** 微信用户头像 */
|
||||||
|
wxAvatar?: string;
|
||||||
}
|
}
|
||||||
export interface Ab98UserDetailDTO {
|
export interface Ab98UserDetailDTO {
|
||||||
/** 主键ID */
|
/** 主键ID */
|
||||||
|
|
@ -87,7 +89,8 @@ export interface Ab98UserQuery extends BasePageQuery {
|
||||||
tel?: string;
|
tel?: string;
|
||||||
/** 身份证号码 */
|
/** 身份证号码 */
|
||||||
idnum?: string;
|
idnum?: string;
|
||||||
tagName?: string;
|
/** 标签ID,多个标签ID用逗号分隔 */
|
||||||
|
tagIds?: string;
|
||||||
/** 绑定汇邦云 false未绑定 true已绑定 null 全部 */
|
/** 绑定汇邦云 false未绑定 true已绑定 null 全部 */
|
||||||
hasAb98UserId?: boolean;
|
hasAb98UserId?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,23 @@ const drawerRules = reactive({
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getIconByName = (name: string) => {
|
||||||
|
switch (name) {
|
||||||
|
case 'Search':
|
||||||
|
return useRenderIcon(Search);
|
||||||
|
case 'Refresh':
|
||||||
|
return useRenderIcon(Refresh);
|
||||||
|
case 'Plus':
|
||||||
|
return useRenderIcon(Plus);
|
||||||
|
case 'Edit':
|
||||||
|
return useRenderIcon(Edit);
|
||||||
|
case 'Delete':
|
||||||
|
return useRenderIcon(Delete);
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList();
|
getList();
|
||||||
});
|
});
|
||||||
|
|
@ -265,19 +282,19 @@ onMounted(() => {
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" :icon="useRenderIcon(Search)" :loading="pageLoading" @click="onSearch">
|
<el-button type="primary" :icon="getIconByName('Search')" :loading="pageLoading" @click="onSearch">
|
||||||
搜索
|
搜索
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
|
<el-button :icon="getIconByName('Refresh')" @click="resetForm(formRef)">
|
||||||
重置
|
重置
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item class="space-item">
|
<el-form-item class="space-item">
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="success" :icon="useRenderIcon(Plus)" @click="() => openDrawer()">
|
<el-button type="success" :icon="getIconByName('Plus')" @click="() => openDrawer()">
|
||||||
新增标签
|
新增标签
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
@ -311,10 +328,10 @@ onMounted(() => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<el-button type="primary" :icon="useRenderIcon(Edit)" size="small" @click.stop="openDrawer(item)">
|
<el-button type="primary" :icon="getIconByName('Edit')" size="small" @click.stop="openDrawer(item)">
|
||||||
编辑
|
编辑
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="danger" :icon="useRenderIcon(Delete)" size="small" @click.stop="handleDelete(item)">
|
<el-button type="danger" :icon="getIconByName('Delete')" size="small" @click.stop="handleDelete(item)">
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,15 @@ async function fetchAvailableTags() {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 100
|
pageSize: 100
|
||||||
});
|
});
|
||||||
|
// 过滤掉用户已经拥有的标签(如果用户标签已加载)
|
||||||
|
if (tags.value.length > 0) {
|
||||||
|
const userTagIds = tags.value.map(tag => tag.tagId);
|
||||||
|
console.log('userTagIds:', userTagIds);
|
||||||
|
console.log('availableTags before filter:', data.rows);
|
||||||
|
availableTags.value = data.rows.filter(tag => !userTagIds.includes(tag.id));
|
||||||
|
} else {
|
||||||
availableTags.value = data.rows;
|
availableTags.value = data.rows;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch available tags:', error);
|
console.error('Failed to fetch available tags:', error);
|
||||||
}
|
}
|
||||||
|
|
@ -190,6 +198,8 @@ async function handleAddTag() {
|
||||||
showAddTagDialog.value = false;
|
showAddTagDialog.value = false;
|
||||||
addTagForm.value.tagId = null;
|
addTagForm.value.tagId = null;
|
||||||
await fetchUserTags();
|
await fetchUserTags();
|
||||||
|
// 重新获取可用标签列表,过滤掉用户新添加的标签
|
||||||
|
await fetchAvailableTags();
|
||||||
ElMessage.success('标签添加成功');
|
ElMessage.success('标签添加成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('标签添加失败');
|
ElMessage.error('标签添加失败');
|
||||||
|
|
@ -256,16 +266,24 @@ async function handleShowAddTagDialog() {
|
||||||
<template v-if="userType === 'ab98'">
|
<template v-if="userType === 'ab98'">
|
||||||
<el-descriptions-item label="性别">{{ (userInfo as Ab98UserDetailDTO).sex }}</el-descriptions-item>
|
<el-descriptions-item label="性别">{{ (userInfo as Ab98UserDetailDTO).sex }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="手机号">{{ userInfo.tel }}</el-descriptions-item>
|
<el-descriptions-item label="手机号">{{ userInfo.tel }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="身份证号">{{ (userInfo as Ab98UserDetailDTO).idnum }}</el-descriptions-item>
|
<el-descriptions-item label="身份证">{{ (userInfo as Ab98UserDetailDTO).idnum }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="住址">{{ (userInfo as Ab98UserDetailDTO).address }}</el-descriptions-item>
|
<el-descriptions-item label="住址">{{ (userInfo as Ab98UserDetailDTO).address }}</el-descriptions-item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<el-descriptions-item label="OpenID">{{ (userInfo as WxUserDTO).openid }}</el-descriptions-item>
|
<el-descriptions-item label="OpenID">{{ (userInfo as WxUserDTO).openid }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="昵称">{{ (userInfo as WxUserDTO).nickName }}</el-descriptions-item>
|
<el-descriptions-item label="昵称">{{ (userInfo as WxUserDTO).nickName }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="手机号">{{ userInfo.tel }}</el-descriptions-item>
|
<el-descriptions-item label="手机号">{{ userInfo.tel }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="微信余额">{{ formatFenToYuan((userInfo as WxUserDTO).wxBalance)
|
|
||||||
}}</el-descriptions-item>
|
|
||||||
</template>
|
</template>
|
||||||
|
<el-descriptions-item label="标签">
|
||||||
|
<div class="tag-container">
|
||||||
|
<div class="user-tags" v-if="tags.length > 0">
|
||||||
|
<el-tag v-for="tag in tags" :key="tag.id" class="tag-item" closable @close="handleDeleteTag(tag.id)">
|
||||||
|
{{ tag.tagName }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" size="small" @click="handleShowAddTagDialog">添加标签</el-button>
|
||||||
|
</div>
|
||||||
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card class="info-card">
|
<el-card class="info-card">
|
||||||
|
|
@ -294,17 +312,6 @@ async function handleShowAddTagDialog() {
|
||||||
}}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
<el-descriptions-item label="已使用借呗">{{ formatFenToYuan((userInfo as Ab98UserDetailDTO).useBalance)
|
<el-descriptions-item label="已使用借呗">{{ formatFenToYuan((userInfo as Ab98UserDetailDTO).useBalance)
|
||||||
}}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
<el-descriptions-item label="标签" :span="2">
|
|
||||||
<div class="tag-container">
|
|
||||||
<div class="user-tags" v-if="tags.length > 0">
|
|
||||||
<el-tag v-for="tag in tags" :key="tag.id" class="tag-item" closable
|
|
||||||
@close="handleDeleteTag(tag.id)">
|
|
||||||
{{ tag.tagName }}
|
|
||||||
</el-tag>
|
|
||||||
</div>
|
|
||||||
<el-button type="primary" size="small" @click="handleShowAddTagDialog">添加标签</el-button>
|
|
||||||
</div>
|
|
||||||
</el-descriptions-item>
|
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|
@ -312,8 +319,6 @@ async function handleShowAddTagDialog() {
|
||||||
<el-descriptions-item label="微信用户ID">{{ (userInfo as WxUserDTO).wxUserId }}</el-descriptions-item>
|
<el-descriptions-item label="微信用户ID">{{ (userInfo as WxUserDTO).wxUserId }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="OpenID">{{ openid }}</el-descriptions-item>
|
<el-descriptions-item label="OpenID">{{ openid }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="创建时间" :span="2">{{ userInfo.createTime }}</el-descriptions-item>
|
<el-descriptions-item label="创建时间" :span="2">{{ userInfo.createTime }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="微信余额" :span="2">{{ formatFenToYuan((userInfo as WxUserDTO).wxBalance)
|
|
||||||
}}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="备注" :span="2">{{ (userInfo as WxUserDTO).remark || '无'
|
<el-descriptions-item label="备注" :span="2">{{ (userInfo as WxUserDTO).remark || '无'
|
||||||
}}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
@ -549,5 +554,13 @@ async function handleShowAddTagDialog() {
|
||||||
.el-pagination {
|
.el-pagination {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-details {
|
||||||
|
:deep(.el-descriptions__label) {
|
||||||
|
width: 70px;
|
||||||
|
min-width: 70px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -12,7 +12,7 @@ import Refresh from "@iconify-icons/ep/refresh";
|
||||||
import Plus from "@iconify-icons/ep/plus";
|
import Plus from "@iconify-icons/ep/plus";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||||
import { getAb98UserTagNamesApi } from "@/api/ab98/tag";
|
import { getMembershipTagListApi, type MembershipTagQuery } from "@/api/membership/membershipTag";
|
||||||
import { formatFenToYuan } from "@/utils/currency";
|
import { formatFenToYuan } from "@/utils/currency";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
|
@ -22,13 +22,13 @@ defineOptions({
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
const wxStore = useWxStore();
|
const wxStore = useWxStore();
|
||||||
const tagOptions = ref<string[]>([]);
|
const tagOptions = ref<{ label: string; value: number }[]>([]);
|
||||||
const searchFormParams = reactive<Ab98UserQuery & { search?: string }>({
|
const searchFormParams = reactive<Ab98UserQuery & { search?: string; tagIds?: string }>({
|
||||||
corpid: wxStore.corpid || "",
|
corpid: wxStore.corpid || "",
|
||||||
name: undefined,
|
name: undefined,
|
||||||
tel: undefined,
|
tel: undefined,
|
||||||
idnum: undefined,
|
idnum: undefined,
|
||||||
tagName: undefined,
|
tagIds: undefined,
|
||||||
hasAb98UserId: undefined,
|
hasAb98UserId: undefined,
|
||||||
search: undefined
|
search: undefined
|
||||||
});
|
});
|
||||||
|
|
@ -62,8 +62,16 @@ async function onSearch() {
|
||||||
async function getList() {
|
async function getList() {
|
||||||
CommonUtils.fillPaginationParams(searchFormParams, pagination);
|
CommonUtils.fillPaginationParams(searchFormParams, pagination);
|
||||||
|
|
||||||
|
// 处理标签ID数组转换为逗号分隔的字符串
|
||||||
|
const params = { ...searchFormParams };
|
||||||
|
if (params.tagIds && Array.isArray(params.tagIds) && params.tagIds.length > 0) {
|
||||||
|
params.tagIds = params.tagIds.join(',');
|
||||||
|
} else {
|
||||||
|
Reflect.deleteProperty(params, 'tagIds');
|
||||||
|
}
|
||||||
|
|
||||||
pageLoading.value = true;
|
pageLoading.value = true;
|
||||||
const { data } = await getAb98UserListApiWithWx(searchFormParams).finally(
|
const { data } = await getAb98UserListApiWithWx(params).finally(
|
||||||
() => {
|
() => {
|
||||||
pageLoading.value = false;
|
pageLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
@ -98,6 +106,7 @@ const resetForm = formEl => {
|
||||||
if (!formEl) return;
|
if (!formEl) return;
|
||||||
formEl.resetFields();
|
formEl.resetFields();
|
||||||
searchFormParams.hasAb98UserId = undefined;
|
searchFormParams.hasAb98UserId = undefined;
|
||||||
|
searchFormParams.tagIds = undefined;
|
||||||
onSearch();
|
onSearch();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -172,10 +181,26 @@ const addMemberRules = reactive({
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList();
|
getList();
|
||||||
getAb98UserTagNamesApi().then(res => {
|
getMembershipTagListApi({ corpid: wxStore.corpid || "" }).then(res => {
|
||||||
tagOptions.value = res.data;
|
tagOptions.value = res.data.rows.map(tag => ({
|
||||||
|
label: tag.name || '',
|
||||||
|
value: tag.id || 0
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
const getIconByName = (name: string) => {
|
||||||
|
switch (name) {
|
||||||
|
case 'Search':
|
||||||
|
return useRenderIcon(Search);
|
||||||
|
case 'Refresh':
|
||||||
|
return useRenderIcon(Refresh);
|
||||||
|
case 'Plus':
|
||||||
|
return useRenderIcon(Plus);
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -187,11 +212,12 @@ onMounted(() => {
|
||||||
<el-input v-model="searchFormParams.search" placeholder="请输入姓名/手机号/身份证" clearable class="!w-[300px]"
|
<el-input v-model="searchFormParams.search" placeholder="请输入姓名/手机号/身份证" clearable class="!w-[300px]"
|
||||||
@keydown.enter.prevent="onSearch" @change="handleSearchInput" />
|
@keydown.enter.prevent="onSearch" @change="handleSearchInput" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- <el-form-item prop="tagName">
|
<el-form-item prop="tagIds">
|
||||||
<el-select v-model="searchFormParams.tagName" placeholder="请选择用户标签" clearable class="!w-[160px]">
|
<el-select v-model="searchFormParams.tagIds" placeholder="会员标签" multiple clearable collapse-tags
|
||||||
<el-option v-for="item in tagOptions" :key="item" :label="item" :value="item" />
|
class="!w-[200px]">
|
||||||
|
<el-option v-for="item in tagOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item> -->
|
</el-form-item>
|
||||||
<el-form-item prop="hasAb98UserId">
|
<el-form-item prop="hasAb98UserId">
|
||||||
<el-select v-model="searchFormParams.hasAb98UserId" placeholder="绑定汇邦云状态" clearable class="!w-[160px]">
|
<el-select v-model="searchFormParams.hasAb98UserId" placeholder="绑定汇邦云状态" clearable class="!w-[160px]">
|
||||||
<el-option label="全部" :value="null" />
|
<el-option label="全部" :value="null" />
|
||||||
|
|
@ -200,20 +226,20 @@ onMounted(() => {
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" :icon="useRenderIcon(Search)" :loading="pageLoading" @click="onSearch">
|
<el-button type="primary" :icon="getIconByName('Search')" :loading="pageLoading" @click="onSearch">
|
||||||
搜索
|
搜索
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
|
<el-button :icon="getIconByName('Refresh')" @click="resetForm(formRef)">
|
||||||
重置
|
重置
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item class="space-item">
|
<el-form-item class="space-item">
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="success" :icon="useRenderIcon(Plus)" @click="openAddMemberDialog">
|
<el-button type="success" :icon="getIconByName('Plus')" @click="openAddMemberDialog">
|
||||||
添加会员
|
关联会员
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
@ -224,8 +250,8 @@ onMounted(() => {
|
||||||
:xs="24" :sm="12" :md="8" :lg="6">
|
:xs="24" :sm="12" :md="8" :lg="6">
|
||||||
<el-card class="user-card" :body-style="{ padding: '8px 20px' }" @click="handleViewDetail(item)">
|
<el-card class="user-card" :body-style="{ padding: '8px 20px' }" @click="handleViewDetail(item)">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<el-avatar :size="80" :src="item.faceImg" fit="cover" shape="square" class="avatar">
|
<el-avatar :size="80" :src="item.faceImg || item.wxAvatar" fit="cover" shape="square" class="avatar">
|
||||||
<template v-if="!item.faceImg">
|
<template v-if="!item.faceImg && !item.wxAvatar">
|
||||||
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||||
<circle cx="50" cy="50" r="48" fill="#f5f5f5" stroke="#e0e0e0" stroke-width="1" />
|
<circle cx="50" cy="50" r="48" fill="#f5f5f5" stroke="#e0e0e0" stroke-width="1" />
|
||||||
<circle cx="50" cy="40" r="12" fill="#9e9e9e" />
|
<circle cx="50" cy="40" r="12" fill="#9e9e9e" />
|
||||||
|
|
@ -261,8 +287,8 @@ onMounted(() => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 添加会员弹窗 -->
|
<!-- 关联会员弹窗 -->
|
||||||
<el-dialog v-model="dialogVisible" title="添加会员" width="500px" :close-on-click-modal="false">
|
<el-dialog v-model="dialogVisible" title="关联会员" width="500px" :close-on-click-modal="false">
|
||||||
<el-form ref="addMemberFormRef" :model="addMemberForm" :rules="addMemberRules" label-width="100px">
|
<el-form ref="addMemberFormRef" :model="addMemberForm" :rules="addMemberRules" label-width="100px">
|
||||||
<el-form-item label="动态码" prop="dynamicCode">
|
<el-form-item label="动态码" prop="dynamicCode">
|
||||||
<el-input v-model="addMemberForm.dynamicCode" placeholder="请输入动态码" />
|
<el-input v-model="addMemberForm.dynamicCode" placeholder="请输入动态码" />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue