Compare commits

..

6 Commits

Author SHA1 Message Date
dzq 5eb7ae6f00 feat(订单模块): 添加微信openid查询支持并优化用户详情页订单展示
- 在订单查询接口中添加微信openid字段,支持通过openid查询订单
- 重构用户详情页,使用el-descriptions组件优化基本信息展示
- 添加订单列表展示功能,支持分页、加载状态及订单详情查看
2025-05-13 15:34:05 +08:00
dzq 4146851ac2 feat(柜体管理): 添加格口号列和柜机列表功能
在柜体管理页面中,新增了格口号列以展示单元格编号,并添加了左侧柜机列表,方便用户快速切换柜体进行管理。同时优化了页面布局和样式,提升了用户体验。
2025-05-13 10:42:57 +08:00
dzq 312fb7ae81 feat(用户管理): 新增ab98用户详情页面及相关API
添加ab98用户详情页面,包括用户基础信息展示、订单记录等模块。同时新增相关API接口用于获取用户详情数据,并调整路由配置以支持详情页面的访问。
2025-05-13 08:03:00 +08:00
dzq 43cfec18ab fix(user): 修复用户头像未显示时的默认图标
在用户头像未提供时,添加默认的SVG图标作为占位符,提升用户体验。同时移除性别显示,简化用户信息展示。
2025-05-10 15:41:06 +08:00
dzq bdb5a1128c refactor(订单页面): 移除默认选择第一个柜子并触发查询的逻辑
移除 `onMounted` 钩子中默认选择第一个柜子并触发查询的逻辑,改为由用户手动选择柜子后触发查询,以提高用户体验和灵活性。
2025-05-10 09:54:09 +08:00
dzq 42fee5ed40 feat(订单管理): 添加柜机筛选功能并优化格口显示
在订单管理页面中新增了柜机筛选功能,用户可以通过左侧的柜机列表筛选订单。同时,在格口开启记录页面中添加了格口ID的显示列,以方便用户查看和管理格口信息。
2025-05-09 16:06:06 +08:00
13 changed files with 926 additions and 214 deletions

104
src/api/ab98/user.ts Normal file
View File

@ -0,0 +1,104 @@
import { http } from "@/utils/http";
export interface Ab98UserDTO {
/** 主键ID */
ab98UserId?: number;
/** openid */
openid?: string;
/** 汇邦云用户唯一ID */
userid?: string;
/** 真实姓名 */
name?: string;
/** 手机号码 */
tel?: string;
/** 身份证号码 */
idnum?: string;
/** 性别(男 女) */
sex?: string;
/** 人脸照片地址 */
faceImg?: string;
/** 身份证正面地址 */
idcardFront?: string;
/** 身份证背面地址 */
idcardBack?: string;
/** 身份证登记地址 */
address?: string;
/** 是否已注册0未注册 1已注册 */
registered?: boolean;
}
export interface Ab98UserDetailDTO {
/** 主键ID */
ab98UserId?: number;
/** openid */
openid?: string;
/** 汇邦云用户唯一ID */
userid?: string;
/** 真实姓名 */
name?: string;
/** 手机号码 */
tel?: string;
/** 身份证号码 */
idnum?: string;
/** 性别(男 女) */
sex?: string;
/** 人脸照片地址 */
faceImg?: string;
/** 身份证正面地址 */
idcardFront?: string;
/** 身份证背面地址 */
idcardBack?: string;
/** 身份证登记地址 */
address?: string;
/** 是否已注册0未注册 1已注册 */
registered?: boolean;
createTime?: string;
}
export interface Ab98UserQuery extends BasePageQuery {
/** 真实姓名 */
name?: string;
/** 手机号码 */
tel?: string;
/** 身份证号码 */
idnum?: string;
}
export interface AddAb98UserCommand {
/** openid */
openid?: string;
/** 汇邦云用户唯一ID */
userid?: string;
/** 真实姓名 */
name?: string;
/** 手机号码 */
tel?: string;
/** 身份证号码 */
idnum?: string;
/** 性别(男 女) */
sex?: string;
}
export interface UpdateAb98UserCommand extends AddAb98UserCommand {
/** 主键ID */
ab98UserId: number;
}
export const getAb98UserListApi = (params: Ab98UserQuery) => {
return http.request<ResponseData<PageDTO<Ab98UserDTO>>>("get", "/ab98/users", { params });
};
export const addAb98UserApi = (data: AddAb98UserCommand) => {
return http.request<ResponseData<void>>("post", "/ab98/users", { data });
};
export const updateAb98UserApi = (id: number, data: UpdateAb98UserCommand) => {
return http.request<ResponseData<void>>("put", `/ab98/users/${id}`, { data });
};
export const deleteAb98UserApi = (ids: number[]) => {
return http.request<ResponseData<void>>("delete", `/ab98/users/${ids.join(',')}`);
};
export const getAb98UserDetailApi = (id: number) => {
return http.request<ResponseData<Ab98UserDetailDTO>>("get", `/ab98/users/detail/${id}`);
};

View File

@ -3,6 +3,12 @@ import { http } from "@/utils/http";
export interface OrderQuery extends BasePageQuery {
/** 订单编号 */
orderId?: number;
/**
* openid
*/
openid?: string;
/** 柜机id */
cabinetId?: number;
/** 格口id */
cellId?: number;
/**

View File

@ -15,6 +15,14 @@ export default {
meta: {
title: "个人中心"
}
},
{
path: "/user/ab98/detail",
name: "ab98Detail",
component: () => import("@/views/user/ab98/detail.vue"),
meta: {
title: "会员详情"
}
}
]
} as RouteConfigsTable;

View File

@ -174,4 +174,8 @@
padding-bottom: 10px;
box-shadow: 0 -1px 0 0 #e0e3e8, 0 -3px 6px 0 rgb(69 98 155 / 12%);
}
}
.el-card.is-always-shadow {
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.04)!important;
}

View File

@ -4,6 +4,7 @@ import { PureTableBar } from "@/components/RePureTableBar";
import { onBeforeRouteUpdate, useRoute } from "vue-router";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { getCabinetCellList, deleteCabinetCell, CabinetCellDTO } from "@/api/cabinet/cabinet-cell";
import { allCabinets, SmartCabinetDTO } from "@/api/cabinet/smart-cabinet";
import EditPen from "@iconify-icons/ep/edit-pen";
import Delete from "@iconify-icons/ep/delete";
import AddFill from "@iconify-icons/ri/add-circle-line";
@ -14,39 +15,56 @@ import CellEditModal from "./cell-edit-modal.vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { on } from "events";
//
defineOptions({
name: "CabinetCell"
name: "CabinetCell" //
});
//
const formRef = ref();
//
const tableRef = ref();
//
const modalVisible = ref(false);
//
const searchFormParams = ref({
cabinetId: null,
cellNo: null,
cellType: null
cabinetId: null, // ID
cellNo: null, //
cellType: null //
});
//
const pagination = ref({
pageSize: 10,
currentPage: 1,
total: 0
pageSize: 10, //
currentPage: 1, //
total: 0 //
});
//
const loading = ref(false);
//
const dataList = ref([]);
//
const multipleSelection = ref<number[]>([]);
//
const editVisible = ref(false);
//
const currentRow = ref<CabinetCellDTO>();
//
const route = useRoute();
onMounted(() => {
//
const cabinets = ref<SmartCabinetDTO[]>([]);
onMounted(async () => {
const { data } = await allCabinets();
cabinets.value = data;
if (route.query.cabinetId) {
searchFormParams.value.cabinetId = Number(route.query.cabinetId);
getList();
}
getList();
});
onBeforeRouteUpdate(() => {
if (route.query.cabinetId) {
@ -55,24 +73,33 @@ onBeforeRouteUpdate(() => {
}
});
/**
* 获取单元格列表数据
* @returns {Promise<void>}
*/
const getList = async () => {
try {
loading.value = true;
loading.value = true; //
// API
const { data } = await getCabinetCellList({
...searchFormParams.value,
pageSize: pagination.value.pageSize,
pageNum: pagination.value.currentPage
...searchFormParams.value, //
pageSize: pagination.value.pageSize, //
pageNum: pagination.value.currentPage //
});
dataList.value = data.rows;
pagination.value.total = data.total;
dataList.value = data.rows; //
pagination.value.total = data.total; //
} finally {
loading.value = false;
loading.value = false; //
}
};
/**
* 搜索按钮点击事件
* 重置页码为第一页并重新获取数据
*/
const onSearch = () => {
pagination.value.currentPage = 1;
getList();
pagination.value.currentPage = 1; //
getList(); //
};
const resetForm = () => {
@ -90,13 +117,18 @@ const onCurrentChange = (val: number) => {
getList();
};
/**
* 删除单元格
* @param {CabinetCellDTO} row - 要删除的单元格数据
*/
const handleDelete = async (row: CabinetCellDTO) => {
try {
// API
await deleteCabinetCell(row.cellId!.toString());
ElMessage.success("删除成功");
getList();
ElMessage.success("删除成功"); //
getList(); //
} catch (error) {
console.error("删除失败", error);
console.error("删除失败", error); //
}
};
@ -128,6 +160,11 @@ const handleEdit = (row: CabinetCellDTO) => {
editVisible.value = true;
};
/**
* 转换单元格类型为中文描述
* @param {number} cellType - 单元格类型编号
* @returns {string} 单元格类型中文描述
*/
const switchCellType = (cellType: number) => {
switch (cellType) {
case 1:
@ -146,89 +183,120 @@ const switchCellType = (cellType: number) => {
</script>
<template>
<div class="main">
<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="柜体ID" prop="cabinetId">
<el-input v-model.number="searchFormParams.cabinetId" placeholder="请输入柜体ID" clearable class="!w-[200px]" />
</el-form-item>
<el-form-item label="单元格号:" prop="cellNo">
<el-input v-model.number="searchFormParams.cellNo" placeholder="请输入单元格号" clearable class="!w-[180px]" />
</el-form-item>
<el-form-item label="单元格类型:" prop="cellType">
<el-select v-model="searchFormParams.cellType" placeholder="请选择类型" clearable class="!w-[180px]">
<el-option label="小格" :value="1" />
<el-option label="中格" :value="2" />
<el-option label="大格" :value="3" />
<el-option label="超大格" :value="4" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="useRenderIcon(Search)" @click="onSearch">
搜索
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm">
重置
</el-button>
</el-form-item>
</el-form>
<div class="main flex">
<!-- 左侧柜机列表 -->
<div class="w-[200px] pr-4 border-r h-full left-list">
<div class="text-lg font-bold mb-4 px-2">柜机列表</div>
<div class="h-[calc(100vh-180px)] overflow-y-auto">
<div class="cabinet-list">
<div v-for="cabinet in cabinets" :key="cabinet.cabinetId"
class="cabinet-item p-3 mb-2 cursor-pointer rounded hover:bg-gray-100 transition-colors"
:class="{ 'bg-blue-50': searchFormParams.cabinetId === cabinet.cabinetId }"
@click="searchFormParams.cabinetId = cabinet.cabinetId; onSearch()">
{{ cabinet.cabinetName }}
</div>
</div>
</div>
</div>
<PureTableBar title="柜体单元格管理" @refresh="getList">
<template #buttons>
<el-button type="primary" :icon="useRenderIcon(AddFill)" @click="modalVisible = true">
新增单元格
</el-button>
<el-button type="danger" :icon="useRenderIcon(Delete)" :disabled="multipleSelection.length === 0"
@click="handleBulkDelete">
批量删除
</el-button>
</template>
<el-table ref="tableRef" v-loading="loading" :data="dataList" row-key="cellId"
@selection-change="handleSelectionChange" border>
<el-table-column type="selection" width="55" />
<el-table-column label="柜体ID" prop="cabinetId" width="100" />
<el-table-column label="单元格号" prop="cellNo" width="120" />
<el-table-column label="针脚号" prop="pinNo" width="120" />
<el-table-column label="单元格类型" prop="cellType">
<template #default="{ row }">
{{ switchCellType(row.cellType) }}
</template>
</el-table-column>
<el-table-column label="使用状态" prop="usageStatus" width="120">
<template #default="{ row }">
{{ { 1: '空闲', 2: '已占用' }[row.usageStatus] || '未知' }}
</template>
</el-table-column>
<el-table-column label="可用状态" prop="availableStatus" width="120">
<template #default="{ row }">
<el-tag :type="row.availableStatus === 1 ? 'success' : 'danger'">
{{ { 1: '正常', 2: '故障' }[row.availableStatus] || '未知' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button type="primary" link :icon="useRenderIcon(EditPen)" @click="handleEdit(row)">
编辑
</el-button>
<el-popconfirm :title="`确认删除【${row.cellNo}】号单元格?`" @confirm="handleDelete(row)">
<template #reference>
<el-button type="danger" link :icon="useRenderIcon(Delete)">
删除
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-pagination v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"
@size-change="onSizeChange" @current-change="onCurrentChange" />
</PureTableBar>
<cell-form-modal v-model:visible="modalVisible" :initial-cabinet-id="searchFormParams.cabinetId"
@refresh="getList" />
<cell-edit-modal v-model:visible="editVisible" :row="currentRow" @refresh="getList" />
<!-- 右侧内容 -->
<div class="flex-1 pl-4">
<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="柜体ID" prop="cabinetId">
<el-input v-model.number="searchFormParams.cabinetId" placeholder="请输入柜体ID" clearable class="!w-[200px]" />
</el-form-item>
<el-form-item label="单元格号:" prop="cellNo">
<el-input v-model.number="searchFormParams.cellNo" placeholder="请输入单元格号" clearable class="!w-[180px]" />
</el-form-item>
<el-form-item label="单元格类型:" prop="cellType">
<el-select v-model="searchFormParams.cellType" placeholder="请选择类型" clearable class="!w-[180px]">
<el-option label="小格" :value="1" />
<el-option label="中格" :value="2" />
<el-option label="大格" :value="3" />
<el-option label="超大格" :value="4" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="useRenderIcon(Search)" @click="onSearch">
搜索
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm">
重置
</el-button>
</el-form-item>
</el-form>
<PureTableBar title="柜体单元格管理" @refresh="getList">
<template #buttons>
<el-button type="primary" :icon="useRenderIcon(AddFill)" @click="modalVisible = true">
新增单元格
</el-button>
<el-button type="danger" :icon="useRenderIcon(Delete)" :disabled="multipleSelection.length === 0"
@click="handleBulkDelete">
批量删除
</el-button>
</template>
<el-table ref="tableRef" v-loading="loading" :data="dataList" row-key="cellId"
@selection-change="handleSelectionChange" border>
<el-table-column type="selection" width="55" />
<el-table-column label="柜体ID" prop="cabinetId" width="100" />
<el-table-column label="单元格号" prop="cellNo" width="120" />
<el-table-column label="针脚号" prop="pinNo" width="120" />
<el-table-column label="单元格类型" prop="cellType">
<template #default="{ row }">
{{ switchCellType(row.cellType) }}
</template>
</el-table-column>
<el-table-column label="使用状态" prop="usageStatus" width="120">
<template #default="{ row }">
{{ { 1: '空闲', 2: '已占用' }[row.usageStatus] || '未知' }}
</template>
</el-table-column>
<el-table-column label="可用状态" prop="availableStatus" width="120">
<template #default="{ row }">
<el-tag :type="row.availableStatus === 1 ? 'success' : 'danger'">
{{ { 1: '正常', 2: '故障' }[row.availableStatus] || '未知' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button type="primary" link :icon="useRenderIcon(EditPen)" @click="handleEdit(row)">
编辑
</el-button>
<el-popconfirm :title="`确认删除【${row.cellNo}】号单元格?`" @confirm="handleDelete(row)">
<template #reference>
<el-button type="danger" link :icon="useRenderIcon(Delete)">
删除
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-pagination v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"
@size-change="onSizeChange" @current-change="onCurrentChange" />
</PureTableBar>
<cell-form-modal v-model:visible="modalVisible" :initial-cabinet-id="searchFormParams.cabinetId"
@refresh="getList" />
<cell-edit-modal v-model:visible="editVisible" :row="currentRow" @refresh="getList" />
</div>
</div>
</template>
<style scoped></style>
<style lang="scss" scoped>
.flex {
display: flex;
}
.border-r {
border-right: 1px solid #ebeef5;
}
.left-list {
background: #FFFFFF;
padding: 15px;
}
</style>

View File

@ -111,6 +111,7 @@ getList();
<PureTableBar title="格口开启记录" @refresh="getList">
<el-table ref="tableRef" v-loading="loading" :data="dataList" row-key="operationId" border>
<el-table-column label="操作ID" prop="operationId" width="120" />
<el-table-column label="格口ID" prop="cellId" width="120" />
<el-table-column label="操作人" prop="name" width="120" />
<el-table-column label="手机号" prop="mobile" width="120" />
<el-table-column label="商品名称" prop="goodsName" width="180" />

View File

@ -12,6 +12,7 @@ import Qrcode from "@iconify-icons/ep/iphone";
import { ElMessage, ElMessageBox } from "element-plus";
import ShopFormModal from "./shop-form-modal.vue";
import ReQrcode from "@/components/ReQrcode";
import { copyTextToClipboard } from "@pureadmin/utils";
defineOptions({
name: "Shop"
@ -123,6 +124,12 @@ const showQrCode = (shopId: number) => {
qrVisible.value = true;
};
const copyLink = () => {
const url = `http://wxshop.ab98.cn/shop-api/api/shop/wechatAuth?shopId=${currentShopId.value}`;
const success = copyTextToClipboard(url);
success ? ElMessage.success('链接复制成功') : ElMessage.error('复制失败,请手动复制');
};
getList();
</script>
@ -188,6 +195,9 @@ getList();
<ReQrcode :text="`http://wxshop.ab98.cn/shop-api/api/shop/wechatAuth?shopId=${currentShopId}`"
:options="{ width: 200 }" />
<div class="mt-2 text-sm text-gray-500">微信扫码访问</div>
<el-button class="mt-2" type="primary" size="small" @click="copyLink">
复制链接
</el-button>
</div>
</el-dialog>
</div>

View File

@ -199,8 +199,7 @@ getList();
<el-button type="primary" link :icon="useRenderIcon(EditPen)" @click="handleEdit(row)">
编辑
</el-button>
<el-button type="warning" link :icon="useRenderIcon('fluent:cell-phone-arrow-right-20-regular')"
@click="goCellManagement(row)">
<el-button type="warning" link :icon="useRenderIcon(EditPen)" @click="goCellManagement(row)">
格口
</el-button>
<el-button type="warning" link :icon="useRenderIcon('ant-design:gateway-outlined')"

View File

@ -261,6 +261,7 @@ const handleClearGoods = async (row: CabinetCellDTO) => {
<el-table ref="tableRef" v-loading="loading" :data="dataList" row-key="cellId"
@selection-change="handleSelectionChange" border>
<el-table-column label="格口ID" prop="cellId" width="80" />
<el-table-column label="格口号" prop="cellNo" width="80" />
<el-table-column label="商品图片" width="120">
<template #default="{ row }">
<el-image :src="row.coverImg" :preview-src-list="[row.coverImg]" :z-index="9999"

View File

@ -1,8 +1,9 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { ref, watch, onMounted } from "vue";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { getOrderListApi, exportOrderExcelApi, type OrderDTO, OrderQuery } from "@/api/shop/order";
import { allCabinets, SmartCabinetDTO } from "@/api/cabinet/smart-cabinet";
import Search from "@iconify-icons/ep/search";
import Refresh from "@iconify-icons/ep/refresh";
import { ElMessage } from "element-plus";
@ -15,8 +16,10 @@ const route = useRoute();
const formRef = ref();
const tableRef = ref();
const cabinets = ref<SmartCabinetDTO[]>([]);
const searchFormParams = ref<OrderQuery>({
cabinetId: null,
orderId: null,
cellId: null,
status: null,
@ -36,6 +39,11 @@ const pagination = ref({
const loading = ref(false);
const dataList = ref<OrderDTO[]>([]);
onMounted(async () => {
const { data } = await allCabinets();
cabinets.value = data;
});
const getList = async () => {
try {
loading.value = true;
@ -107,125 +115,138 @@ getList();
</script>
<template>
<div class="main">
<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="时间范围:">
<div class="main flex">
<!-- 左侧柜机列表 -->
<div class="w-[200px] mr-4 bg-white rounded shadow p-4">
<div class="text-lg font-bold mb-4">柜机列表</div>
<div class="space-y-2">
<div v-for="cabinet in cabinets" :key="cabinet.cabinetId" class="p-2 rounded cursor-pointer hover:bg-gray-100"
:class="{ 'bg-blue-50': searchFormParams.cabinetId === cabinet.cabinetId }"
@click="searchFormParams.cabinetId = cabinet.cabinetId; onSearch()">
{{ cabinet.cabinetName }}
</div>
</div>
</div>
<div class="flex-1">
<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="时间范围:">
<el-date-picker v-model="searchFormParams.startTime" type="date" placeholder="开始时间"
value-format="YYYY-MM-DD HH:mm:ss" class="!w-[200px]" />
<span class="mx-2"></span>
<el-date-picker v-model="searchFormParams.endTime" type="date" placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss" class="!w-[200px]" />
</el-form-item> -->
<el-form-item label="支付日期:">
<el-date-picker v-model="searchFormParams.payTime" type="date" placeholder="支付时间"
value-format="YYYY-MM-DD HH:mm:ss" class="!w-[200px]" />
</el-form-item>
<el-form-item label="订单编号:" prop="orderId">
<el-input v-model="searchFormParams.orderId" placeholder="请输入订单编号" clearable class="!w-[200px]" />
</el-form-item>
<el-form-item label="格口ID" prop="cellId">
<el-input v-model="searchFormParams.cellId" placeholder="请输入格口ID" clearable class="!w-[200px]" />
</el-form-item>
<el-form-item label="订单状态:" prop="status">
<el-select v-model="searchFormParams.status" placeholder="请选择状态" clearable class="!w-[180px]">
<el-option label="待付款" :value="1" />
<el-option label="已付款" :value="2" />
<el-option label="已发货" :value="3" />
<el-option label="已完成" :value="4" />
<el-option label="已取消" :value="5" />
</el-select>
</el-form-item>
<el-form-item label="支付状态:" prop="payStatus">
<el-select v-model="searchFormParams.payStatus" placeholder="请选择支付状态" clearable class="!w-[180px]">
<el-option label="未支付" :value="1" />
<el-option label="已支付" :value="2" />
<el-option label="退款中" :value="3" />
<el-option label="已退款" :value="4" />
</el-select>
</el-form-item>
<el-form-item label="支付方式:" prop="paymentMethod">
<el-select v-model="searchFormParams.paymentMethod" placeholder="请选择支付方式" clearable class="!w-[180px]">
<el-option label="微信支付" value="wechat" />
<el-option label="余额支付" value="balance" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="useRenderIcon(Search)" @click="onSearch">
搜索
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm">
重置
</el-button>
<el-button type="success" :loading="exportLoading" :icon="useRenderIcon('vscode-icons:file-type-excel2')"
@click="handleExport">
导出Excel
</el-button>
</el-form-item>
</el-form>
<el-form-item label="支付日期:">
<el-date-picker v-model="searchFormParams.payTime" type="date" placeholder="支付时间"
value-format="YYYY-MM-DD HH:mm:ss" class="!w-[200px]" />
</el-form-item>
<el-form-item label="订单编号:" prop="orderId">
<el-input v-model="searchFormParams.orderId" placeholder="请输入订单编号" clearable class="!w-[200px]" />
</el-form-item>
<el-form-item label="格口ID" prop="cellId">
<el-input v-model="searchFormParams.cellId" placeholder="请输入格口ID" clearable class="!w-[200px]" />
</el-form-item>
<el-form-item label="订单状态:" prop="status">
<el-select v-model="searchFormParams.status" placeholder="请选择状态" clearable class="!w-[180px]">
<el-option label="待付款" :value="1" />
<el-option label="已付款" :value="2" />
<el-option label="已发货" :value="3" />
<el-option label="已完成" :value="4" />
<el-option label="已取消" :value="5" />
</el-select>
</el-form-item>
<el-form-item label="支付状态:" prop="payStatus">
<el-select v-model="searchFormParams.payStatus" placeholder="请选择支付状态" clearable class="!w-[180px]">
<el-option label="未支付" :value="1" />
<el-option label="已支付" :value="2" />
<el-option label="退款中" :value="3" />
<el-option label="已退款" :value="4" />
</el-select>
</el-form-item>
<el-form-item label="支付方式:" prop="paymentMethod">
<el-select v-model="searchFormParams.paymentMethod" placeholder="请选择支付方式" clearable class="!w-[180px]">
<el-option label="微信支付" value="wechat" />
<el-option label="余额支付" value="balance" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="useRenderIcon(Search)" @click="onSearch">
搜索
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm">
重置
</el-button>
<el-button type="success" :loading="exportLoading" :icon="useRenderIcon('vscode-icons:file-type-excel2')"
@click="handleExport">
导出Excel
</el-button>
</el-form-item>
</el-form>
<PureTableBar title="订单列表" @refresh="getList">
<el-table ref="tableRef" v-loading="loading" :data="dataList" row-key="orderId" border>
<el-table-column label="订单ID" prop="orderId" width="120" />
<el-table-column label="商品名称" prop="goodsNames" width="180">
<template #default="{ row }">
<span v-if="row.goodsNames">
{{ row.goodsNames.split(',').join(', ') }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="商品封面" prop="coverImgs" width="100">
<template #default="{ row }">
<div v-if="row.coverImgs" class="flex gap-2">
<el-image v-for="(img, index) in row.coverImgs.split(',')" :key="index" :src="img"
:preview-src-list="row.coverImgs.split(',')" :z-index="9999" :preview-teleported="true"
:hide-on-click-modal="true" fit="cover" class="rounded" width="60" height="60" />
</div>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="姓名" prop="name" width="120" />
<el-table-column label="手机号" prop="mobile" width="120" />
<el-table-column label="订单金额" prop="totalAmount" width="120">
<template #default="{ row }">{{ row.totalAmount }}</template>
</el-table-column>
<el-table-column label="订单状态" prop="status" width="120">
<template #default="{ row }">
<el-tag :type="row.status === 2 ? 'success' : row.status === 5 ? 'danger' : 'info'">
{{ { 1: '待付款', 2: '已付款', 3: '已发货', 4: '已完成', 5: '已取消' }[row.status] }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="支付状态" prop="payStatus" width="120">
<template #default="{ row }">
<el-tag :type="row.payStatus === 2 ? 'success' : 'info'">
{{ { 1: '未支付', 2: '已支付', 3: '退款中', 4: '已退款' }[row.payStatus] }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="支付方式" prop="paymentMethod" width="120">
<template #default="{ row }">
{{ { wechat: '微信支付', balance: '余额支付' }[row.paymentMethod] || row.paymentMethod }}
</template>
</el-table-column>
<el-table-column label="支付时间" prop="payTime" width="180">
<template #default="{ row }">
{{ row.payTime ? new Date(row.payTime).toLocaleString() : '-' }}
</template>
</el-table-column>
<!-- <el-table-column label="操作" width="100" fixed="right">
<PureTableBar title="订单列表" @refresh="getList">
<el-table ref="tableRef" v-loading="loading" :data="dataList" row-key="orderId" border>
<el-table-column label="订单ID" prop="orderId" width="120" />
<el-table-column label="商品名称" prop="goodsNames" width="180">
<template #default="{ row }">
<span v-if="row.goodsNames">
{{ row.goodsNames.split(',').join(', ') }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="商品封面" prop="coverImgs" width="100">
<template #default="{ row }">
<div v-if="row.coverImgs" class="flex gap-2">
<el-image v-for="(img, index) in row.coverImgs.split(',')" :key="index" :src="img"
:preview-src-list="row.coverImgs.split(',')" :z-index="9999" :preview-teleported="true"
:hide-on-click-modal="true" fit="cover" class="rounded" width="60" height="60" />
</div>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="姓名" prop="name" width="120" />
<el-table-column label="手机号" prop="mobile" width="120" />
<el-table-column label="订单金额" prop="totalAmount" width="120">
<template #default="{ row }">{{ row.totalAmount }}</template>
</el-table-column>
<el-table-column label="订单状态" prop="status" width="120">
<template #default="{ row }">
<el-tag :type="row.status === 2 ? 'success' : row.status === 5 ? 'danger' : 'info'">
{{ { 1: '待付款', 2: '已付款', 3: '已发货', 4: '已完成', 5: '已取消' }[row.status] }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="支付状态" prop="payStatus" width="120">
<template #default="{ row }">
<el-tag :type="row.payStatus === 2 ? 'success' : 'info'">
{{ { 1: '未支付', 2: '已支付', 3: '退款中', 4: '已退款' }[row.payStatus] }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="支付方式" prop="paymentMethod" width="120">
<template #default="{ row }">
{{ { wechat: '微信支付', balance: '余额支付' }[row.paymentMethod] || row.paymentMethod }}
</template>
</el-table-column>
<el-table-column label="支付时间" prop="payTime" width="180">
<template #default="{ row }">
{{ row.payTime ? new Date(row.payTime).toLocaleString() : '-' }}
</template>
</el-table-column>
<!-- <el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleDetail(row)">
详情
</el-button>
</template>
</el-table-column> -->
</el-table>
<el-pagination v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize"
:page-sizes="[5, 10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"
@size-change="onSizeChange" @current-change="onCurrentChange" />
</PureTableBar>
</el-table>
<el-pagination v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize"
:page-sizes="[5, 10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"
@size-change="onSizeChange" @current-change="onCurrentChange" />
</PureTableBar>
</div>
</div>
</template>

View File

@ -0,0 +1,253 @@
<script setup lang="ts">
import { ref, onMounted, watch } from "vue";
import { useRoute } from "vue-router";
import { type Ab98UserDetailDTO, getAb98UserDetailApi } from "@/api/ab98/user";
import { getOrderListApi, type OrderDTO } from "@/api/shop/order";
defineOptions({
name: "Ab98UserDetail"
});
const route = useRoute();
const userInfo = ref<Ab98UserDetailDTO>({});
const loading = ref(false);
//
const basicInfo = ref({
registerTime: "2023-01-15",
lastLogin: "2023-06-20",
loginCount: 42,
device: "iPhone 13"
});
//
const orderRecords = ref<OrderDTO[]>([]);
const pagination = ref({
pageSize: 5,
currentPage: 1,
total: 0
});
const orderLoading = ref(false);
const activeTab = ref('basic');
async function fetchOrders() {
try {
orderLoading.value = true;
const { data } = await getOrderListApi({
openid: userInfo.value.openid,
pageSize: pagination.value.pageSize,
pageNum: pagination.value.currentPage
});
orderRecords.value = data.rows;
pagination.value.total = data.total;
} finally {
orderLoading.value = false;
}
}
watch(activeTab, (newVal) => {
if (newVal === 'orders') {
fetchOrders();
}
});
async function fetchUserDetail() {
try {
loading.value = true;
const userId = route.query.id;
const { data } = await getAb98UserDetailApi(Number(userId));
userInfo.value = data;
} finally {
loading.value = false;
}
}
onMounted(() => {
fetchUserDetail();
});
</script>
<template>
<div class="detail-container">
<div class="flex-container">
<el-card class="user-info-card">
<div class="user-header">
<el-avatar :size="100" :src="userInfo.faceImg" fit="cover" shape="square">
<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="40" r="12" fill="#9e9e9e" />
<rect x="40" y="52" width="20" height="30" rx="2" fill="#9e9e9e" />
</svg>
</el-avatar>
<div class="user-name">{{ userInfo.name }}</div>
</div>
<el-divider />
<el-descriptions class="user-details" :column="1" border>
<el-descriptions-item label="性别">{{ userInfo.sex }}</el-descriptions-item>
<el-descriptions-item label="手机号">{{ userInfo.tel }}</el-descriptions-item>
<el-descriptions-item label="身份证号">{{ userInfo.idnum }}</el-descriptions-item>
<el-descriptions-item label="住址">{{ userInfo.address }}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card class="info-card">
<div class="tab-header">
<el-tabs type="card" v-model="activeTab">
<el-tab-pane label="基础信息" name="basic"></el-tab-pane>
<el-tab-pane label="订单记录" name="orders"></el-tab-pane>
</el-tabs>
</div>
<el-descriptions class="info-details" v-if="activeTab === 'basic'" :column="2" border>
<el-descriptions-item label="会员ID">{{ userInfo.ab98UserId }}</el-descriptions-item>
<el-descriptions-item label="微信openid">{{ userInfo.openid }}</el-descriptions-item>
<el-descriptions-item label="注册时间">{{ userInfo.createTime }}</el-descriptions-item>
<el-descriptions-item label="最后登录">{{ basicInfo.lastLogin }}</el-descriptions-item>
<el-descriptions-item label="登录次数">{{ basicInfo.loginCount }}</el-descriptions-item>
<el-descriptions-item label="常用设备">{{ basicInfo.device }}</el-descriptions-item>
</el-descriptions>
<div class="info-details" v-if="activeTab === 'orders'">
<PureTableBar title="订单列表" @refresh="fetchOrders">
<el-table ref="tableRef" v-loading="orderLoading" :data="orderRecords" row-key="orderId" border>
<el-table-column label="订单ID" prop="orderId" width="120" />
<el-table-column label="商品名称" prop="goodsNames" width="180">
<template #default="{ row }">
<span v-if="row.goodsNames">
{{ row.goodsNames.split(',').join(', ') }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="商品封面" prop="coverImgs" width="100">
<template #default="{ row }">
<div v-if="row.coverImgs" class="flex gap-2">
<el-image v-for="(img, index) in row.coverImgs.split(',')" :key="index" :src="img"
:preview-src-list="row.coverImgs.split(',')" :z-index="9999" :preview-teleported="true"
:hide-on-click-modal="true" fit="cover" class="rounded" width="60" height="60" />
</div>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="姓名" prop="name" width="120" />
<el-table-column label="手机号" prop="mobile" width="120" />
<el-table-column label="订单金额" prop="totalAmount" width="120">
<template #default="{ row }">{{ row.totalAmount }}</template>
</el-table-column>
<el-table-column label="订单状态" prop="status" width="120">
<template #default="{ row }">
<el-tag :type="row.status === 2 ? 'success' : row.status === 5 ? 'danger' : 'info'">
{{ { 1: '待付款', 2: '已付款', 3: '已发货', 4: '已完成', 5: '已取消' }[row.status] }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="支付状态" prop="payStatus" width="120">
<template #default="{ row }">
<el-tag :type="row.payStatus === 2 ? 'success' : 'info'">
{{ { 1: '未支付', 2: '已支付', 3: '退款中', 4: '已退款' }[row.payStatus] }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="支付方式" prop="paymentMethod" width="120">
<template #default="{ row }">
{{ { wechat: '微信支付', balance: '余额支付' }[row.paymentMethod] || row.paymentMethod }}
</template>
</el-table-column>
<el-table-column label="支付时间" prop="payTime" width="180">
<template #default="{ row }">
{{ row.payTime ? new Date(row.payTime).toLocaleString() : '-' }}
</template>
</el-table-column>
</el-table>
<el-pagination v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize"
:page-sizes="[5, 10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"
@size-change="fetchOrders" @current-change="fetchOrders" />
</PureTableBar>
</div>
</el-card>
</div>
</div>
</template>
<style scoped lang="scss">
.detail-container {
display: flex;
flex-direction: column;
height: 100%;
.flex-container {
display: flex;
flex: 1;
gap: 20px;
min-height: 0;
.user-info-card {
width: 20%;
}
.info-card {
width: 80%;
}
}
.user-info-card,
.shop-info-card {
height: 100%;
min-height: 85vh;
}
.user-header {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
.user-name {
margin-top: 15px;
font-size: 20px;
font-weight: bold;
}
}
.tab-header {
margin-bottom: 20px;
}
.order-item {
margin-bottom: 20px;
padding: 10px;
background-color: #f9f9f9;
border-radius: 4px;
.order-id {
font-weight: bold;
margin-bottom: 8px;
}
.order-detail {
display: flex;
justify-content: space-between;
font-size: 13px;
color: #666;
}
}
.detail-item {
margin: 15px 0;
font-size: 14px;
.label {
color: #606266;
margin-right: 10px;
}
.value {
color: #303133;
}
}
}
</style>

View File

@ -0,0 +1,226 @@
<script setup lang="ts">
import { onMounted, reactive, ref, watch } from "vue";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { getAb98UserListApi, type Ab98UserDTO, Ab98UserQuery } from "@/api/ab98/user";
import { type PaginationProps } from "@pureadmin/table";
import { CommonUtils } from "@/utils/common";
import Search from "@iconify-icons/ep/search";
import Refresh from "@iconify-icons/ep/refresh";
import View from "@iconify-icons/ep/view";
import { useRouter } from "vue-router";
defineOptions({
name: "Ab98User"
});
const router = useRouter();
const formRef = ref();
const searchFormParams = reactive<Ab98UserQuery>({
name: undefined,
tel: undefined,
idnum: undefined
});
const pageLoading = ref(false);
const dataList = ref<Ab98UserDTO[]>([]);
const pagination = reactive<PaginationProps>({
total: 0,
pageSize: 12,
currentPage: 1,
background: true
});
async function onSearch() {
pagination.currentPage = 1;
getList();
}
async function getList() {
CommonUtils.fillPaginationParams(searchFormParams, pagination);
pageLoading.value = true;
const { data } = await getAb98UserListApi(searchFormParams).finally(
() => {
pageLoading.value = false;
}
);
dataList.value = data.rows;
pagination.total = data.total;
}
const resetForm = formEl => {
if (!formEl) return;
formEl.resetFields();
onSearch();
};
const handleViewDetail = (row: any) => {
router.push({
path: '/user/ab98/detail',
query: {
id: row.ab98UserId
}
});
};
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 w-[99/100] pl-8 pt-[12px]">
<el-form-item label="姓名:" prop="name">
<el-input v-model="searchFormParams.name" placeholder="请输入" clearable class="!w-[160px]" />
</el-form-item>
<el-form-item label="手机号:" prop="tel">
<el-input v-model="searchFormParams.tel" placeholder="请输入" clearable class="!w-[160px]" />
</el-form-item>
<el-form-item label="身份证:" prop="idnum">
<el-input v-model="searchFormParams.idnum" placeholder="请输入" clearable class="!w-[160px]" />
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="useRenderIcon(Search)" :loading="pageLoading" @click="onSearch">
搜索
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
重置
</el-button>
</el-form-item>
</el-form>
<div class="grid-container">
<el-row :gutter="20">
<el-col v-for="(item, index) in dataList" :key="item.ab98UserId" :xs="24" :sm="12" :md="8" :lg="6">
<el-card class="user-card" :body-style="{ padding: '8px 20px' }">
<div class="card-content">
<el-avatar :size="80" :src="item.faceImg" fit="cover" shape="square" class="avatar">
<template v-if="!item.faceImg">
<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="40" r="12" fill="#9e9e9e" />
<rect x="40" y="52" width="20" height="30" rx="2" fill="#9e9e9e" />
</svg>
</template>
</el-avatar>
<div class="user-info">
<div class="name">姓名{{ item.name }}</div>
<div class="gender">性别{{ item.sex === '男' ? '男' : item.sex === '女' ? '女' : '' }}</div>
<div class="tel">电话{{ item.tel }}</div>
</div>
</div>
<div class="idnum">身份证号{{ item.idnum }}</div>
<div class="address"> {{ item.address }}</div>
<el-divider class="divider" />
<el-button class="detail-btn" :icon="useRenderIcon(View)" @click="handleViewDetail(item)" />
</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>
</div>
</template>
<style scoped lang="scss">
:deep(.el-dropdown-menu__item i) {
margin: 0;
}
.search-form {
:deep(.el-form-item) {
margin-bottom: 12px;
}
}
.user-card {
margin-bottom: 20px;
min-height: 210px;
display: flex;
flex-direction: column;
justify-content: space-between;
.card-content {
flex: 1;
display: flex;
flex-direction: row;
margin: 15px 0px;
gap: 15px;
.avatar {
align-self: flex-start;
}
.user-info {
text-align: left;
flex: 1;
.name,
.gender,
.tel {
font-size: 14px;
color: #606266;
margin-bottom: 6px;
line-height: 1.5;
}
.name {
font-weight: 500;
color: #303133;
}
}
.idnum,
.address {
font-size: 13px;
color: #606266;
margin: 4px 0;
line-height: 1.5;
word-break: break-all;
}
}
.divider {
margin: 10px 0px;
}
.detail-btn {
width: 100%;
border: 0;
margin-top: auto;
padding: 12px 0;
}
}
.grid-container {
margin: 20px 0;
padding-bottom: 60px;
position: relative;
.el-row {
margin-bottom: -20px;
}
}
.pagination-wrapper {
position: relative;
background: var(--el-bg-color);
padding: 12px 12px;
margin-top: 20px;
text-align: center;
:deep(.el-pagination) {
margin: 0;
padding: 8px 0;
}
}
</style>

View File

@ -60,10 +60,21 @@ watch(
<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" fit="cover" shape="square" class="avatar" />
<el-avatar :size="60" :src="item.avatar" fit="cover" shape="square" class="avatar">
<template v-if="!item.avatar">
<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="40" r="12" fill="#9e9e9e" />
<!-- 修改后的身体部分 - 矩形躯干 -->
<rect x="40" y="52" width="20" height="30" rx="2" fill="#9e9e9e" />
</svg>
</template>
</el-avatar>
<div class="user-info">
<div class="name">{{ item.name }}</div>
<div class="gender">性别{{ item.gender === '1' ? '男' : item.gender === '2' ? '女' : '' }}</div>
<!-- <div class="gender">性别{{ item.gender === '1' ? '男' : item.gender === '2' ? '女' : '' }}</div> -->
<div class="create-time">创建时间{{ item.createTimeStr }}</div>
<div class="balance">余额¥{{ item.balance?.toFixed(2) }}</div>
</div>