核销功能

This commit is contained in:
2026-07-06 22:22:21 +08:00
parent 386ee9e754
commit d708a10095
3 changed files with 47 additions and 27 deletions
+10 -12
View File
@@ -1,6 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { import {
Alert, Button, Card, Col, Form, Input, InputNumber, Row, Space, Tabs, Typography, message, Button, Card, Col, Form, Input, InputNumber, Row, Space, Tabs, Typography, message,
} from 'antd'; } from 'antd';
import { request } from '../lib/api'; import { request } from '../lib/api';
@@ -56,13 +56,6 @@ export default function RedeemDebugPage() {
return ( return (
<div> <div>
<Typography.Title level={4}></Typography.Title> <Typography.Title level={4}></Typography.Title>
<Alert
type="warning"
showIcon
style={{ marginBottom: 16 }}
message="仅用于 preV1 联调"
description="模拟 C 端生成核销码、门店预览与确认核销。确认核销会真实扣减权益并写入核销记录。"
/>
<Tabs <Tabs
items={[ items={[
@@ -74,8 +67,12 @@ export default function RedeemDebugPage() {
<Col xs={24} lg={10}> <Col xs={24} lg={10}>
<Card size="small" title="参数"> <Card size="small" title="参数">
<Form form={createForm} layout="vertical"> <Form form={createForm} layout="vertical">
<Form.Item name="userId" label="用户 ID" rules={[{ required: true }]}> <Form.Item
<Input placeholder="用户表 id" /> name="userId"
label="用户"
rules={[{ required: true, message: '请填写用户 ID、编号或手机号' }]}
>
<Input placeholder="数据库 ID / 用户编号 / 手机号" />
</Form.Item> </Form.Item>
<Form.Item name="amount" label="核销金额" rules={[{ required: true }]}> <Form.Item name="amount" label="核销金额" rules={[{ required: true }]}>
<InputNumber min={0.01} max={500} step={1} style={{ width: '100%' }} /> <InputNumber min={0.01} max={500} step={1} style={{ width: '100%' }} />
@@ -90,8 +87,9 @@ export default function RedeemDebugPage() {
type="primary" type="primary"
loading={loading} loading={loading}
onClick={() => { onClick={() => {
const values = createForm.getFieldsValue(); void createForm.validateFields().then((values) => {
void invoke('/admin/redeem/debug/create-token', values, setCreateResult, '核销码已生成'); void invoke('/admin/redeem/debug/create-token', values, setCreateResult, '核销码已生成');
});
}} }}
> >
-8
View File
@@ -102,14 +102,6 @@ export default function MinePage() {
<span className="mine-member-tag">{hasWechat ? '微信会员' : '好客会员'}</span> <span className="mine-member-tag">{hasWechat ? '微信会员' : '好客会员'}</span>
</div> </div>
</div> </div>
<button
type="button"
className="mine-settings-btn"
aria-label="设置"
onClick={() => showToast('preV1:账号设置即将开放')}
>
<span className="material-symbols-outlined">settings</span>
</button>
</div> </div>
<div className="mine-header-glow" aria-hidden /> <div className="mine-header-glow" aria-hidden />
</header> </header>
@@ -13,17 +13,46 @@ export class AdminRedeemDebugService {
private readonly redeemService: RedeemService, private readonly redeemService: RedeemService,
) {} ) {}
private parseId(value: string, label: string): bigint { private parseStoreId(value: string): bigint {
const normalized = value?.trim(); const normalized = String(value ?? '').trim();
if (!normalized || !/^\d+$/.test(normalized)) { if (!normalized || !/^\d+$/.test(normalized)) {
throw new BadRequestException(`${label}格式无效`); throw new BadRequestException('门店 ID 格式无效');
} }
return BigInt(normalized); return BigInt(normalized);
} }
private async resolveUserId(identifier: string): Promise<bigint> {
const normalized = String(identifier ?? '').trim();
if (!normalized) {
throw new BadRequestException('请填写用户 ID、用户编号或手机号');
}
if (/^\d+$/.test(normalized)) {
const byId = await this.prisma.user.findUnique({
where: { id: BigInt(normalized) },
select: { id: true },
});
if (byId) return byId.id;
const byPhone = await this.prisma.user.findFirst({
where: { phone: normalized },
select: { id: true },
});
if (byPhone) return byPhone.id;
} else {
const byNo = await this.prisma.user.findFirst({
where: { userNo: normalized },
select: { id: true },
});
if (byNo) return byNo.id;
}
throw new NotFoundException('用户不存在,请检查 ID、用户编号或手机号');
}
private async resolveStoreAccountId(storeId: string): Promise<bigint> { private async resolveStoreAccountId(storeId: string): Promise<bigint> {
const account = await this.prisma.storeAccount.findFirst({ const account = await this.prisma.storeAccount.findFirst({
where: { storeId: this.parseId(storeId, '门店 ID'), status: 'ACTIVE' }, where: { storeId: this.parseStoreId(storeId), status: 'ACTIVE' },
orderBy: { id: 'asc' }, orderBy: { id: 'asc' },
select: { id: true, store: { select: { name: true } } }, select: { id: true, store: { select: { name: true } } },
}); });
@@ -34,10 +63,11 @@ export class AdminRedeemDebugService {
} }
async createToken(dto: AdminRedeemDebugCreateTokenDto) { async createToken(dto: AdminRedeemDebugCreateTokenDto) {
return this.redeemService.createToken(this.parseId(dto.userId, '用户 ID'), { const userId = await this.resolveUserId(dto.userId);
return this.redeemService.createToken(userId, {
amount: dto.amount, amount: dto.amount,
couponId: dto.couponId, couponId: dto.couponId?.trim() || undefined,
storeId: dto.storeId, storeId: dto.storeId?.trim() || undefined,
}); });
} }