feat(会员标签): 实现会员标签管理功能
添加会员标签相关API接口和页面 重构用户详情页标签管理逻辑 新增标签管理页面,支持增删改查操作
This commit is contained in:
parent
673c992c09
commit
71e52b3505
|
|
@ -0,0 +1,126 @@
|
|||
import { http } from "@/utils/http";
|
||||
|
||||
/**
|
||||
* 会员标签关联数据传输对象
|
||||
*/
|
||||
export interface MembershipTagMemberDTO {
|
||||
/** 主键,自动递增 */
|
||||
id?: number;
|
||||
/** 企业微信ID */
|
||||
corpid?: string;
|
||||
/** 标签ID */
|
||||
tagId?: number;
|
||||
/** 汇邦云用户ID */
|
||||
ab98UserId?: number;
|
||||
/** 微信用户ID */
|
||||
wxUserId?: number;
|
||||
/** 创建者ID */
|
||||
creatorId?: number;
|
||||
/** 创建时间 */
|
||||
createTime?: string;
|
||||
/** 更新者ID */
|
||||
updaterId?: number;
|
||||
/** 更新时间 */
|
||||
updateTime?: string;
|
||||
/** 标签名称(可选,通过关联查询或缓存获取) */
|
||||
tagName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员标签关联查询参数
|
||||
*/
|
||||
export interface SearchMembershipTagMemberQuery extends BasePageQuery {
|
||||
/** 企业微信ID */
|
||||
corpid?: string;
|
||||
/** 标签ID */
|
||||
tagId?: number;
|
||||
/** 汇邦云用户ID */
|
||||
ab98UserId?: number;
|
||||
/** 微信用户ID */
|
||||
wxUserId?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加会员标签关联命令
|
||||
*/
|
||||
export interface AddMembershipTagMemberCommand {
|
||||
/** 企业微信ID */
|
||||
corpid?: string;
|
||||
/** 标签ID */
|
||||
tagId: number;
|
||||
/** 汇邦云用户ID */
|
||||
ab98UserId?: number;
|
||||
/** 微信用户ID */
|
||||
wxUserId?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新会员标签关联命令
|
||||
*/
|
||||
export interface UpdateMembershipTagMemberCommand extends AddMembershipTagMemberCommand {
|
||||
/** 主键,自动递增 */
|
||||
id: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员标签关联列表
|
||||
*/
|
||||
export const getMembershipTagMemberListApi = (params: SearchMembershipTagMemberQuery) => {
|
||||
return http.request<ResponseData<PageDTO<MembershipTagMemberDTO>>>("get", "/membership/tagMembers", { params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取标签关联详情
|
||||
*/
|
||||
export const getMembershipTagMemberDetailApi = (id: number) => {
|
||||
return http.request<ResponseData<MembershipTagMemberDTO>>("get", `/membership/tagMembers/${id}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加会员标签关联
|
||||
*/
|
||||
export const addMembershipTagMemberApi = (data: AddMembershipTagMemberCommand) => {
|
||||
return http.request<ResponseData<void>>("post", "/membership/tagMembers", { data });
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新会员标签关联
|
||||
*/
|
||||
export const updateMembershipTagMemberApi = (id: number, data: UpdateMembershipTagMemberCommand) => {
|
||||
return http.request<ResponseData<void>>("put", `/membership/tagMembers/${id}`, { data });
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除会员标签关联
|
||||
*/
|
||||
export const deleteMembershipTagMemberApi = (ids: number[]) => {
|
||||
return http.request<ResponseData<void>>("delete", `/membership/tagMembers/${ids.join(',')}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据标签ID查询关联用户
|
||||
*/
|
||||
export const getMembershipTagMemberByTagIdApi = (tagId: number) => {
|
||||
return http.request<ResponseData<MembershipTagMemberDTO[]>>("get", `/membership/tagMembers/byTag/${tagId}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据汇邦云用户ID查询标签
|
||||
*/
|
||||
export const getMembershipTagMemberByAb98UserIdApi = (ab98UserId: number) => {
|
||||
return http.request<ResponseData<MembershipTagMemberDTO[]>>("get", `/membership/tagMembers/byAb98User/${ab98UserId}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据微信用户ID查询标签
|
||||
*/
|
||||
export const getMembershipTagMemberByWxUserIdApi = (wxUserId: number) => {
|
||||
return http.request<ResponseData<MembershipTagMemberDTO[]>>("get", `/membership/tagMembers/byWxUser/${wxUserId}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户的所有标签
|
||||
*/
|
||||
export const getUserTagsApi = (params: { corpid: string; ab98UserId?: number; wxUserId?: number }) => {
|
||||
return http.request<ResponseData<MembershipTagMemberDTO[]>>("get", "/membership/tagMembers/userTags", { params });
|
||||
};
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
import { http } from "@/utils/http";
|
||||
|
||||
/**
|
||||
* 会员标签数据传输对象
|
||||
*/
|
||||
export interface MembershipTagDTO {
|
||||
/** 主键,自动递增 */
|
||||
id?: number;
|
||||
/** 企业微信ID */
|
||||
corpid?: string;
|
||||
/** 标签名称 */
|
||||
name?: string;
|
||||
/** 标签颜色(十六进制代码) */
|
||||
color?: string;
|
||||
/** 标签说明 */
|
||||
remark?: string;
|
||||
/** 状态:0-禁用 1-启用 */
|
||||
status?: number;
|
||||
/** 创建者ID */
|
||||
creatorId?: number;
|
||||
/** 创建时间 */
|
||||
createTime?: string;
|
||||
/** 更新者ID */
|
||||
updaterId?: number;
|
||||
/** 更新时间 */
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员标签查询参数
|
||||
*/
|
||||
export interface MembershipTagQuery extends BasePageQuery {
|
||||
/** 企业微信ID */
|
||||
corpid?: string;
|
||||
/** 标签名称 */
|
||||
name?: string;
|
||||
/** 状态:0-禁用 1-启用 */
|
||||
status?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加会员标签命令
|
||||
*/
|
||||
export interface AddMembershipTagCommand {
|
||||
/** 企业微信ID */
|
||||
corpid?: string;
|
||||
/** 标签名称 */
|
||||
name: string;
|
||||
/** 标签颜色(十六进制代码) */
|
||||
color?: string;
|
||||
/** 标签说明 */
|
||||
remark?: string;
|
||||
/** 状态:0-禁用 1-启用 */
|
||||
status?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新会员标签命令
|
||||
*/
|
||||
export interface UpdateMembershipTagCommand extends AddMembershipTagCommand {
|
||||
/** 主键,自动递增 */
|
||||
id: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员标签列表
|
||||
*/
|
||||
export const getMembershipTagListApi = (params: MembershipTagQuery) => {
|
||||
return http.request<ResponseData<PageDTO<MembershipTagDTO>>>("get", "/membership/tags", { params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取标签详情
|
||||
*/
|
||||
export const getMembershipTagDetailApi = (id: number) => {
|
||||
return http.request<ResponseData<MembershipTagDTO>>("get", `/membership/tags/${id}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加会员标签
|
||||
*/
|
||||
export const addMembershipTagApi = (data: AddMembershipTagCommand) => {
|
||||
return http.request<ResponseData<void>>("post", "/membership/tags", { data });
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新会员标签
|
||||
*/
|
||||
export const updateMembershipTagApi = (id: number, data: UpdateMembershipTagCommand) => {
|
||||
return http.request<ResponseData<void>>("put", `/membership/tags/${id}`, { data });
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除会员标签
|
||||
*/
|
||||
export const deleteMembershipTagApi = (ids: number[]) => {
|
||||
return http.request<ResponseData<void>>("delete", `/membership/tags/${ids.join(',')}`);
|
||||
};
|
||||
|
|
@ -0,0 +1,485 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import {
|
||||
getMembershipTagListApi,
|
||||
type MembershipTagDTO,
|
||||
type MembershipTagQuery,
|
||||
addMembershipTagApi,
|
||||
type AddMembershipTagCommand,
|
||||
updateMembershipTagApi,
|
||||
type UpdateMembershipTagCommand,
|
||||
deleteMembershipTagApi
|
||||
} from "@/api/membership/membershipTag";
|
||||
import { type PaginationProps } from "@pureadmin/table";
|
||||
import { CommonUtils } from "@/utils/common";
|
||||
import { useWxStore } from "@/store/modules/wx";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
|
||||
import Search from "@iconify-icons/ep/search";
|
||||
import Refresh from "@iconify-icons/ep/refresh";
|
||||
import Plus from "@iconify-icons/ep/plus";
|
||||
import Edit from "@iconify-icons/ep/edit-pen";
|
||||
import Delete from "@iconify-icons/ep/delete";
|
||||
|
||||
defineOptions({
|
||||
name: "MembershipTag"
|
||||
});
|
||||
|
||||
const wxStore = useWxStore();
|
||||
const formRef = ref();
|
||||
const drawerVisible = ref(false);
|
||||
const drawerLoading = ref(false);
|
||||
const drawerFormRef = ref();
|
||||
const drawerForm = reactive<AddMembershipTagCommand>({
|
||||
corpid: wxStore.corpid || "",
|
||||
name: "",
|
||||
color: "#409EFF",
|
||||
remark: "",
|
||||
status: 1
|
||||
});
|
||||
const isEdit = ref(false);
|
||||
const currentTagId = ref<number | null>(null);
|
||||
|
||||
const pageLoading = ref(false);
|
||||
const dataList = ref<MembershipTagDTO[]>([]);
|
||||
const pagination = reactive<PaginationProps>({
|
||||
total: 0,
|
||||
pageSize: 12,
|
||||
currentPage: 1,
|
||||
background: true
|
||||
});
|
||||
|
||||
const searchFormParams = reactive<MembershipTagQuery & { search?: string }>({
|
||||
corpid: wxStore.corpid || "",
|
||||
name: undefined,
|
||||
status: undefined,
|
||||
search: undefined
|
||||
});
|
||||
|
||||
// 搜索输入处理
|
||||
const handleSearchInput = () => {
|
||||
searchFormParams.name = searchFormParams.search || undefined;
|
||||
};
|
||||
|
||||
// 搜索
|
||||
async function onSearch() {
|
||||
handleSearchInput();
|
||||
pagination.currentPage = 1;
|
||||
getList();
|
||||
}
|
||||
|
||||
// 获取列表
|
||||
async function getList() {
|
||||
CommonUtils.fillPaginationParams(searchFormParams, pagination);
|
||||
|
||||
pageLoading.value = true;
|
||||
try {
|
||||
const { data } = await getMembershipTagListApi(searchFormParams);
|
||||
dataList.value = data.rows;
|
||||
pagination.total = data.total;
|
||||
} catch (error) {
|
||||
console.error("获取标签列表失败:", error);
|
||||
ElMessage.error("获取标签列表失败");
|
||||
} finally {
|
||||
pageLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = formEl => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
// 清空非表单字段
|
||||
searchFormParams.name = undefined;
|
||||
onSearch();
|
||||
};
|
||||
|
||||
// 打开抽屉(新增或编辑)
|
||||
const openDrawer = (tag?: MembershipTagDTO) => {
|
||||
drawerVisible.value = true;
|
||||
if (tag) {
|
||||
// 编辑模式
|
||||
isEdit.value = true;
|
||||
currentTagId.value = tag.id!;
|
||||
drawerForm.name = tag.name || "";
|
||||
drawerForm.color = tag.color || "#409EFF";
|
||||
drawerForm.remark = tag.remark || "";
|
||||
drawerForm.status = tag.status ?? 1;
|
||||
} else {
|
||||
// 新增模式
|
||||
isEdit.value = false;
|
||||
currentTagId.value = null;
|
||||
drawerForm.name = "";
|
||||
drawerForm.color = "#409EFF";
|
||||
drawerForm.remark = "";
|
||||
drawerForm.status = 1;
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭抽屉
|
||||
const closeDrawer = () => {
|
||||
drawerVisible.value = false;
|
||||
drawerFormRef.value?.resetFields();
|
||||
isEdit.value = false;
|
||||
currentTagId.value = null;
|
||||
};
|
||||
|
||||
// 颜色转换函数
|
||||
const convertColorToHex = (color: string): string => {
|
||||
if (!color) return "#409EFF";
|
||||
|
||||
// 如果已经是16进制格式,直接返回
|
||||
if (color.startsWith('#')) {
|
||||
return color.toUpperCase();
|
||||
}
|
||||
|
||||
// 处理RGB格式
|
||||
if (color.startsWith('rgb')) {
|
||||
const rgbMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*\d+\.?\d*)?\)/);
|
||||
if (rgbMatch) {
|
||||
const r = parseInt(rgbMatch[1]);
|
||||
const g = parseInt(rgbMatch[2]);
|
||||
const b = parseInt(rgbMatch[3]);
|
||||
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理HSL格式
|
||||
if (color.startsWith('hsl')) {
|
||||
// 这里可以添加HSL到RGB的转换逻辑,但为了简化,先返回默认值
|
||||
return "#409EFF";
|
||||
}
|
||||
|
||||
return color.toUpperCase();
|
||||
};
|
||||
|
||||
// 处理颜色选择
|
||||
const handleColorChange = (color: string) => {
|
||||
drawerForm.color = convertColorToHex(color);
|
||||
};
|
||||
|
||||
// 提交标签(新增或编辑)
|
||||
const submitDrawer = async () => {
|
||||
if (!drawerFormRef.value) return;
|
||||
|
||||
await drawerFormRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
drawerLoading.value = true;
|
||||
// 确保颜色是16进制格式
|
||||
const processedForm = {
|
||||
...drawerForm,
|
||||
color: convertColorToHex(drawerForm.color)
|
||||
};
|
||||
|
||||
if (isEdit.value && currentTagId.value) {
|
||||
const updateData: UpdateMembershipTagCommand = {
|
||||
id: currentTagId.value,
|
||||
...processedForm
|
||||
};
|
||||
await updateMembershipTagApi(currentTagId.value, updateData);
|
||||
ElMessage.success("标签更新成功");
|
||||
} else {
|
||||
await addMembershipTagApi(processedForm);
|
||||
ElMessage.success("标签添加成功");
|
||||
}
|
||||
closeDrawer();
|
||||
getList();
|
||||
} catch (error) {
|
||||
console.error(isEdit.value ? "更新标签失败:" : "添加标签失败:", error);
|
||||
ElMessage.error(isEdit.value ? "更新标签失败,请重试" : "添加标签失败,请重试");
|
||||
} finally {
|
||||
drawerLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 删除标签
|
||||
const handleDelete = async (row: MembershipTagDTO) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除标签 "${row.name}" 吗?`, "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
});
|
||||
await deleteMembershipTagApi([row.id!]);
|
||||
ElMessage.success("删除成功");
|
||||
getList();
|
||||
} catch (error) {
|
||||
if (error !== "cancel") {
|
||||
console.error("删除标签失败:", error);
|
||||
ElMessage.error("删除失败");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 预定义颜色
|
||||
const predefineColors = [
|
||||
"#409EFF",
|
||||
"#67C23A",
|
||||
"#E6A23C",
|
||||
"#F56C6C",
|
||||
"#909399",
|
||||
"#FF9A6C",
|
||||
"#A0CFFF",
|
||||
"#B3E19D",
|
||||
"#F3D19E",
|
||||
"#FAB6B6",
|
||||
"#C0C4CC",
|
||||
"#FFD7A6"
|
||||
];
|
||||
|
||||
// 表单验证规则
|
||||
const drawerRules = reactive({
|
||||
name: [
|
||||
{ required: true, message: "请输入标签名称", trigger: "blur" }
|
||||
],
|
||||
color: [
|
||||
{ required: true, message: "请选择标签颜色", trigger: "change" }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: "请选择状态", trigger: "change" }
|
||||
]
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<div class="float-right w-full">
|
||||
<el-form ref="formRef" :inline="true" :model="searchFormParams"
|
||||
class="search-form bg-bg_color flex w-[99/100] pl-[22px] pt-[12px]">
|
||||
<el-form-item prop="search">
|
||||
<el-input v-model="searchFormParams.search" placeholder="请输入标签名称" clearable class="!w-[300px]"
|
||||
@keydown.enter.prevent="onSearch" @change="handleSearchInput" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="status">
|
||||
<el-select v-model="searchFormParams.status" placeholder="请选择状态" clearable class="!w-[160px]">
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="禁用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :icon="useRenderIcon(Search)" :loading="pageLoading" @click="onSearch">
|
||||
搜索
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item class="space-item">
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="success" :icon="useRenderIcon(Plus)" @click="() => openDrawer()">
|
||||
新增标签
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="grid-container">
|
||||
<el-row :gutter="12">
|
||||
<el-col v-for="(item, index) in dataList" :key="item.id" :xs="24" :sm="12" :md="8" :lg="6">
|
||||
<el-card class="tag-card" :body-style="{ padding: '16px' }">
|
||||
<div class="card-header">
|
||||
<div class="tag-color" :style="{ backgroundColor: item.color || '#409EFF' }"></div>
|
||||
<div class="tag-name">{{ item.name }}</div>
|
||||
<div class="tag-status">
|
||||
<el-tag :type="item.status === 1 ? 'success' : 'danger'" size="small">
|
||||
{{ item.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tag-remark" v-if="item.remark">{{ item.remark }}</div>
|
||||
<div class="tag-remark empty" v-else>暂无说明</div>
|
||||
<div class="tag-meta">
|
||||
<div class="meta-item">
|
||||
<span class="label">创建时间:</span>
|
||||
<span class="value">{{ item.createTime || '未知' }}</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<span class="label">更新时间:</span>
|
||||
<span class="value">{{ item.updateTime || '未知' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<el-button type="primary" :icon="useRenderIcon(Edit)" size="small" @click.stop="openDrawer(item)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button type="danger" :icon="useRenderIcon(Delete)" size="small" @click.stop="handleDelete(item)">
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination background layout="prev, pager, next" :page-size="pagination.pageSize"
|
||||
:total="pagination.total" v-model:current-page="pagination.currentPage" @current-change="getList"
|
||||
@size-change="getList" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑标签抽屉 -->
|
||||
<el-drawer v-model="drawerVisible" :title="isEdit ? '编辑标签' : '新增标签'" size="400px" :close-on-click-modal="false">
|
||||
<el-form ref="drawerFormRef" :model="drawerForm" :rules="drawerRules" label-width="80px">
|
||||
<el-form-item label="标签名称" prop="name">
|
||||
<el-input v-model="drawerForm.name" placeholder="请输入标签名称" maxlength="20" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="标签颜色" prop="color">
|
||||
<el-color-picker v-model="drawerForm.color" show-alpha :predefine="predefineColors"
|
||||
@change="handleColorChange" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="drawerForm.status">
|
||||
<el-radio :label="1">启用</el-radio>
|
||||
<el-radio :label="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="标签说明" prop="remark">
|
||||
<el-input v-model="drawerForm.remark" type="textarea" placeholder="请输入标签说明" maxlength="100" show-word-limit
|
||||
:rows="3" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div style="flex: auto">
|
||||
<el-button @click="closeDrawer">取消</el-button>
|
||||
<el-button type="primary" :loading="drawerLoading" @click="submitDrawer">{{ isEdit ? '确认更新' : '确认添加'
|
||||
}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-dropdown-menu__item i) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-card {
|
||||
margin-bottom: 12px;
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
|
||||
transform: translateY(-4px);
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.tag-color {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.tag-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tag-status {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
flex: 1;
|
||||
|
||||
.tag-remark {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 12px;
|
||||
min-height: 20px;
|
||||
|
||||
&.empty {
|
||||
color: #909399;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-meta {
|
||||
.meta-item {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.value {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
margin: 12px 0;
|
||||
padding-bottom: 0px;
|
||||
position: relative;
|
||||
|
||||
.el-row {
|
||||
margin-bottom: -20px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
position: relative;
|
||||
background: var(--el-bg-color);
|
||||
padding: 9px 12px;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
|
||||
:deep(.el-pagination) {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.space-item {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -4,7 +4,8 @@ import { useRoute } from "vue-router";
|
|||
import { type Ab98UserDetailDTO, getAb98UserDetailApi } from "@/api/ab98/user";
|
||||
import { type WxUserDTO, getWxUserDetailApi } from "@/api/wx/wxUser";
|
||||
import { getOrderListApi, type OrderDTO } from "@/api/shop/order";
|
||||
import { Ab98UserTagDTO, addAb98UserTagApi, deleteAb98UserTagConfirmApi, getAb98UserTagListApi } from "@/api/ab98/tag";
|
||||
import { MembershipTagMemberDTO, getUserTagsApi, addMembershipTagMemberApi, deleteMembershipTagMemberApi } from "@/api/membership/MembershipTagMember";
|
||||
import { getMembershipTagListApi, addMembershipTagApi, type MembershipTagDTO } from "@/api/membership/membershipTag";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
import { useWxStore } from "@/store/modules/wx";
|
||||
|
|
@ -20,11 +21,12 @@ const wxStore = useWxStore();
|
|||
const userInfo = ref<Ab98UserDetailDTO | WxUserDTO>({});
|
||||
const userType = ref<'ab98' | 'wx'>('ab98'); // 用户类型:ab98用户或微信用户
|
||||
const loading = ref(false);
|
||||
const tags = ref<Ab98UserTagDTO[]>([]);
|
||||
const tags = ref<MembershipTagMemberDTO[]>([]);
|
||||
const tagsLoading = ref(false);
|
||||
const showAddTagDialog = ref(false);
|
||||
const availableTags = ref<MembershipTagDTO[]>([]);
|
||||
const addTagForm = ref({
|
||||
tagName: ''
|
||||
tagId: null as number | null
|
||||
});
|
||||
const balanceVisible = ref(false);
|
||||
|
||||
|
|
@ -102,28 +104,48 @@ async function fetchUserTags() {
|
|||
if (userType.value !== 'ab98') return;
|
||||
|
||||
tagsLoading.value = true;
|
||||
const { data } = await getAb98UserTagListApi({
|
||||
ab98UserId: (userInfo.value as Ab98UserDetailDTO).ab98UserId,
|
||||
pageSize: 6,
|
||||
pageNum: 1
|
||||
const { data } = await getUserTagsApi({
|
||||
corpid: wxStore.corpid,
|
||||
ab98UserId: (userInfo.value as Ab98UserDetailDTO).ab98UserId
|
||||
});
|
||||
tags.value = data.rows;
|
||||
tags.value = data;
|
||||
} finally {
|
||||
tagsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchAvailableTags() {
|
||||
try {
|
||||
const { data } = await getMembershipTagListApi({
|
||||
corpid: wxStore.corpid,
|
||||
pageNum: 1,
|
||||
pageSize: 100
|
||||
});
|
||||
availableTags.value = data.rows;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch available tags:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAddTag() {
|
||||
try {
|
||||
if (userType.value !== 'ab98') return;
|
||||
if (!addTagForm.value.tagId) {
|
||||
ElMessage.warning('请选择一个标签');
|
||||
return;
|
||||
}
|
||||
|
||||
tagsLoading.value = true;
|
||||
await addAb98UserTagApi({
|
||||
ab98UserId: (userInfo.value as Ab98UserDetailDTO).ab98UserId,
|
||||
tagName: addTagForm.value.tagName
|
||||
|
||||
// 将标签关联到用户
|
||||
await addMembershipTagMemberApi({
|
||||
corpid: wxStore.corpid,
|
||||
tagId: addTagForm.value.tagId,
|
||||
ab98UserId: (userInfo.value as Ab98UserDetailDTO).ab98UserId
|
||||
});
|
||||
|
||||
showAddTagDialog.value = false;
|
||||
addTagForm.value.tagName = '';
|
||||
addTagForm.value.tagId = null;
|
||||
await fetchUserTags();
|
||||
ElMessage.success('标签添加成功');
|
||||
} catch (error) {
|
||||
|
|
@ -133,7 +155,7 @@ async function handleAddTag() {
|
|||
}
|
||||
}
|
||||
|
||||
async function handleDeleteTag(tagId: number) {
|
||||
async function handleDeleteTag(id: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这个标签吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
|
|
@ -141,7 +163,7 @@ async function handleDeleteTag(tagId: number) {
|
|||
type: 'warning'
|
||||
});
|
||||
tagsLoading.value = true;
|
||||
await deleteAb98UserTagConfirmApi(tagId);
|
||||
await deleteMembershipTagMemberApi([id]);
|
||||
await fetchUserTags();
|
||||
ElMessage.success('标签删除成功');
|
||||
} catch (error) {
|
||||
|
|
@ -155,11 +177,17 @@ async function handleDeleteTag(tagId: number) {
|
|||
|
||||
onMounted(() => {
|
||||
fetchUserDetail();
|
||||
fetchAvailableTags();
|
||||
});
|
||||
|
||||
async function handleModifyBalance() {
|
||||
balanceVisible.value = true;
|
||||
}
|
||||
|
||||
async function handleShowAddTagDialog() {
|
||||
await fetchAvailableTags();
|
||||
showAddTagDialog.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -219,12 +247,12 @@ async function handleModifyBalance() {
|
|||
<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.tagId" class="tag-item" closable
|
||||
@close="handleDeleteTag(tag.tagId)">
|
||||
<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="showAddTagDialog = true">添加标签</el-button>
|
||||
<el-button type="primary" size="small" @click="handleShowAddTagDialog">添加标签</el-button>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
|
@ -305,8 +333,10 @@ async function handleModifyBalance() {
|
|||
</div>
|
||||
<el-dialog v-model="showAddTagDialog" title="添加标签" width="30%">
|
||||
<el-form :model="addTagForm" label-width="80px">
|
||||
<el-form-item label="标签名称">
|
||||
<el-input v-model="addTagForm.tagName" placeholder="请输入标签名称" />
|
||||
<el-form-item label="选择标签">
|
||||
<el-select v-model="addTagForm.tagId" placeholder="请选择标签" filterable>
|
||||
<el-option v-for="tag in availableTags" :key="tag.id" :label="tag.name" :value="tag.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
|
|
|
|||
Loading…
Reference in New Issue